mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Compare commits
350 Commits
copilot/ad
...
v1.12.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a0dbc472 | ||
|
|
8ab73b58e7 | ||
|
|
3cad13f679 | ||
|
|
e4c35bd74c | ||
|
|
ff7c84b867 | ||
|
|
f00c098bf7 | ||
|
|
c61fa33c23 | ||
|
|
e3ed7f8c7b | ||
|
|
31d24fa6a9 | ||
|
|
98b155379b | ||
|
|
9a0e0cccfe | ||
|
|
6ce4e9b50c | ||
|
|
bc5eb28299 | ||
|
|
156495f3a6 | ||
|
|
eaa8821a23 | ||
|
|
85bb54b988 | ||
|
|
909e7a2ca5 | ||
|
|
18f1fc8349 | ||
|
|
adfeccf06f | ||
|
|
72274de26e | ||
|
|
c0a787db2b | ||
|
|
209f6e27e0 | ||
|
|
d16f04942f | ||
|
|
5dafcf2170 | ||
|
|
56ab840f37 | ||
|
|
db94be5084 | ||
|
|
88d82eee4b | ||
|
|
c164011a4e | ||
|
|
e7dd82fab0 | ||
|
|
25b235a1b1 | ||
|
|
e07a1af84b | ||
|
|
e0960bd8e7 | ||
|
|
f17d02c992 | ||
|
|
2e37061278 | ||
|
|
e76138c35f | ||
|
|
adbb169463 | ||
|
|
b008283a7f | ||
|
|
82603c3abe | ||
|
|
df6581abab | ||
|
|
0450069179 | ||
|
|
9b5d507804 | ||
|
|
92b4422572 | ||
|
|
d8d1573b0d | ||
|
|
00a988c2ad | ||
|
|
89bb424ce2 | ||
|
|
1a45978a2d | ||
|
|
2bb603aafd | ||
|
|
b74b71ecb3 | ||
|
|
65a256f7a8 | ||
|
|
b1819cb61b | ||
|
|
52ca6cb63a | ||
|
|
234861710f | ||
|
|
e90025aa76 | ||
|
|
7a09cd2772 | ||
|
|
a654ab1e89 | ||
|
|
3c338c7d8a | ||
|
|
bf2efa3332 | ||
|
|
d28a8673b5 | ||
|
|
0a861141fa | ||
|
|
f831be849b | ||
|
|
a00d12a8dd | ||
|
|
23be238414 | ||
|
|
3b8f724a96 | ||
|
|
61dc82f423 | ||
|
|
843b4a93fe | ||
|
|
272e75f9a0 | ||
|
|
f331f05f9a | ||
|
|
0a21d2b8d0 | ||
|
|
ee1fa5b464 | ||
|
|
6b30370210 | ||
|
|
85aefec45a | ||
|
|
3bf23479cf | ||
|
|
f7a8a315e7 | ||
|
|
b18cea6d0f | ||
|
|
6966df5de7 | ||
|
|
e8c136169d | ||
|
|
da2ad48b92 | ||
|
|
b6349ac163 | ||
|
|
65c03f1013 | ||
|
|
fcdaa2d6b3 | ||
|
|
bbc85af0bd | ||
|
|
58eb74778b | ||
|
|
dd7fe47136 | ||
|
|
d81ed3eb4c | ||
|
|
ef62360342 | ||
|
|
3eb83b5b2d | ||
|
|
e54d291989 | ||
|
|
9d4117ae18 | ||
|
|
aa89297297 | ||
|
|
4cedd8fc23 | ||
|
|
65e339bed1 | ||
|
|
a8bd5c65cc | ||
|
|
8b72a21977 | ||
|
|
dbdd38e421 | ||
|
|
46a866ece9 | ||
|
|
664c75ebba | ||
|
|
be2bec4ead | ||
|
|
0e1d691b7f | ||
|
|
18d85f7db1 | ||
|
|
98e4b0035a | ||
|
|
7dc8b036e1 | ||
|
|
a4bc0cfecb | ||
|
|
5c9db19be0 | ||
|
|
15dee375d2 | ||
|
|
7e086db901 | ||
|
|
855dc5ba79 | ||
|
|
bb41792de2 | ||
|
|
67f4f5d2c4 | ||
|
|
76aed24192 | ||
|
|
6387822594 | ||
|
|
84aa074d18 | ||
|
|
777c387f1e | ||
|
|
91e29e815d | ||
|
|
da6da096b8 | ||
|
|
0500446b36 | ||
|
|
ac499dc4bb | ||
|
|
5199e3960e | ||
|
|
39bc769113 | ||
|
|
8a18eac752 | ||
|
|
f9f7abfffe | ||
|
|
4905c858db | ||
|
|
7d835ed934 | ||
|
|
ae47b00c6c | ||
|
|
d199f78d18 | ||
|
|
25c6d432ac | ||
|
|
c5ddd5bb7f | ||
|
|
88cb79d400 | ||
|
|
72a4697cda | ||
|
|
17939a3159 | ||
|
|
ddae91f21a | ||
|
|
9185a0a681 | ||
|
|
6234af6680 | ||
|
|
1e30b67b4d | ||
|
|
06cbb86a29 | ||
|
|
4d4848ab68 | ||
|
|
acc31b4448 | ||
|
|
b34706b152 | ||
|
|
b375ccfee7 | ||
|
|
49981f8327 | ||
|
|
90c272a4f2 | ||
|
|
bb09b3e5f1 | ||
|
|
3657275ca0 | ||
|
|
99cfbbc287 | ||
|
|
0d061f680a | ||
|
|
f492978638 | ||
|
|
1f4e2f33ef | ||
|
|
7b70250d07 | ||
|
|
cd0ffa9359 | ||
|
|
ea03b75b20 | ||
|
|
d83a27471b | ||
|
|
5c04eab617 | ||
|
|
9ce196dc3c | ||
|
|
bd572fcd8f | ||
|
|
712cbdbe1f | ||
|
|
4476697867 | ||
|
|
43186c4304 | ||
|
|
79b488d5c4 | ||
|
|
07bd7b0128 | ||
|
|
0f1439a065 | ||
|
|
cab4e108c8 | ||
|
|
a55b4462d4 | ||
|
|
246d503dea | ||
|
|
795390b033 | ||
|
|
4dc392b213 | ||
|
|
8ac21ed2d4 | ||
|
|
6ed3aaf3b6 | ||
|
|
db9b930abc | ||
|
|
63ce6b60d5 | ||
|
|
7a5e247d01 | ||
|
|
795560dba2 | ||
|
|
6661a20564 | ||
|
|
a3b8c1a80e | ||
|
|
460418f035 | ||
|
|
ea4d01f22f | ||
|
|
e5fb8b1fb6 | ||
|
|
0762cce09a | ||
|
|
b84206157a | ||
|
|
8084f80259 | ||
|
|
77e3708bf7 | ||
|
|
5ff054ef51 | ||
|
|
f463560ead | ||
|
|
d985acae68 | ||
|
|
72bb109a68 | ||
|
|
1fdce39622 | ||
|
|
5c1a76f8da | ||
|
|
b6b262d8bb | ||
|
|
eebd6ac4df | ||
|
|
0ec119e727 | ||
|
|
de5fd9d641 | ||
|
|
e546ffb37a | ||
|
|
c2b195d3c2 | ||
|
|
3e6c39e2ee | ||
|
|
71dbab2235 | ||
|
|
80405bef73 | ||
|
|
f3da720d8b | ||
|
|
1641f5e308 | ||
|
|
d99f4cc83d | ||
|
|
f4de46d2ac | ||
|
|
eaf5b17592 | ||
|
|
6de8049ab5 | ||
|
|
f312883f39 | ||
|
|
ada93c7182 | ||
|
|
e928358dfb | ||
|
|
ae0648fca8 | ||
|
|
d4cdc5172c | ||
|
|
790501e51f | ||
|
|
7b643a09d5 | ||
|
|
c6721ada67 | ||
|
|
3714e9b393 | ||
|
|
27d64ec8ad | ||
|
|
43014ea54b | ||
|
|
3d06cd4910 | ||
|
|
8691607ade | ||
|
|
db4153cc90 | ||
|
|
c984a6cdc8 | ||
|
|
060b6c4363 | ||
|
|
6a5a150722 | ||
|
|
62d50a3f13 | ||
|
|
56d53adab1 | ||
|
|
8f677417d1 | ||
|
|
3d5903a45f | ||
|
|
ba77356d98 | ||
|
|
5604fb2362 | ||
|
|
b23562ea20 | ||
|
|
0afeae16de | ||
|
|
749f94e885 | ||
|
|
5188629256 | ||
|
|
9117c0dd41 | ||
|
|
7d72ca329c | ||
|
|
df987c84de | ||
|
|
16274ba940 | ||
|
|
c7d35daad8 | ||
|
|
1b053efb5a | ||
|
|
803b4d30a6 | ||
|
|
c51fced060 | ||
|
|
aa123b73d1 | ||
|
|
611bbed2df | ||
|
|
8a93ff1ffb | ||
|
|
0641b8ba9c | ||
|
|
7b0bb3f92c | ||
|
|
3f276b58a5 | ||
|
|
75193e58ce | ||
|
|
a4e3415235 | ||
|
|
b8b938c61c | ||
|
|
2899866c63 | ||
|
|
9157831d16 | ||
|
|
cabc0df64d | ||
|
|
c40debc362 | ||
|
|
1784366694 | ||
|
|
8a994d4724 | ||
|
|
abb6e38208 | ||
|
|
92ffbd2b52 | ||
|
|
2b45db42b2 | ||
|
|
fe2a9bea38 | ||
|
|
dd39dbe79c | ||
|
|
e8e1fbb72f | ||
|
|
d546db33ff | ||
|
|
3c35da6029 | ||
|
|
e090e2dbd4 | ||
|
|
6b2cd6e843 | ||
|
|
0728f28cbe | ||
|
|
9e1d592de3 | ||
|
|
f1f2461e09 | ||
|
|
ffb4daf0fb | ||
|
|
d5c89ec65e | ||
|
|
d1c91b41e1 | ||
|
|
906eacd586 | ||
|
|
02772750e1 | ||
|
|
2ba4108c95 | ||
|
|
11daa3e417 | ||
|
|
944a8f16c7 | ||
|
|
70bd46293a | ||
|
|
6eaac7be89 | ||
|
|
2ae7330140 | ||
|
|
1cfd620124 | ||
|
|
f24e5d79bc | ||
|
|
a7c925cba4 | ||
|
|
bbc214fa9b | ||
|
|
8fdc925338 | ||
|
|
4b2883fac6 | ||
|
|
05c32e7f08 | ||
|
|
82608cd4ce | ||
|
|
fcdd4d4a52 | ||
|
|
445f4f2f3e | ||
|
|
b7a1f1a2d1 | ||
|
|
aa1f241894 | ||
|
|
fb364d86fa | ||
|
|
d747fbc95c | ||
|
|
0efe76c62b | ||
|
|
ab6abb799b | ||
|
|
9647e4d6cc | ||
|
|
be82ff5c7f | ||
|
|
efc10a92ef | ||
|
|
3635938b00 | ||
|
|
0cac3c4c23 | ||
|
|
01c5b208e8 | ||
|
|
528f3ab668 | ||
|
|
56b253fb3c | ||
|
|
b3387e96ed | ||
|
|
42f445565d | ||
|
|
d9d24b6605 | ||
|
|
7444bb2c25 | ||
|
|
fdb5324595 | ||
|
|
ed01cfc6db | ||
|
|
213adac2ea | ||
|
|
83fbaa3cb4 | ||
|
|
1ad7b517fa | ||
|
|
6c3d080e83 | ||
|
|
82453c84ba | ||
|
|
c73e56fd7f | ||
|
|
1e5ee1dd37 | ||
|
|
c3015a22cb | ||
|
|
01d26abd5c | ||
|
|
7f4bca0f90 | ||
|
|
7cc76facba | ||
|
|
e491dbb38a | ||
|
|
dd5573bc60 | ||
|
|
bb8996d62b | ||
|
|
2401c0223d | ||
|
|
af3b1c7370 | ||
|
|
979ad07925 | ||
|
|
3e44703cae | ||
|
|
c82f23095a | ||
|
|
187b6607a4 | ||
|
|
5e41ec703d | ||
|
|
ad28216987 | ||
|
|
d2a9560e71 | ||
|
|
41d72e28c3 | ||
|
|
e324ec639b | ||
|
|
cdb04519e2 | ||
|
|
38305cb676 | ||
|
|
2b36087597 | ||
|
|
7af9b80f23 | ||
|
|
60a86590aa | ||
|
|
588b76c2a1 | ||
|
|
f1d5c3374c | ||
|
|
ca16a4c81d | ||
|
|
034d555d30 | ||
|
|
ce6211d252 | ||
|
|
92ff142da2 | ||
|
|
c816f2c905 | ||
|
|
be67aae778 | ||
|
|
301f3d776f | ||
|
|
9eb6e049bd | ||
|
|
1f136c1f85 | ||
|
|
c293f64dc0 | ||
|
|
adbf43fe10 | ||
|
|
aab402731f | ||
|
|
e2b7f648f4 | ||
|
|
427738d81f |
133
CHANGELOG.md
133
CHANGELOG.md
@@ -5,6 +5,133 @@ 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.
|
||||
|
||||
## 1.12.0 (2016-07-14)
|
||||
|
||||
### Builder
|
||||
|
||||
+ New `HEALTHCHECK` Dockerfile instruction to support user-defined healthchecks [#23218](https://github.com/docker/docker/pull/23218)
|
||||
+ New `SHELL` Dockerfile instruction to specify the default shell when using the shell form for commands in a Dockerfile [#22489](https://github.com/docker/docker/pull/22489)
|
||||
+ Add `#escape=` Dockerfile directive to support platform-specific parsing of file paths in Dockerfile [#22268](https://github.com/docker/docker/pull/22268)
|
||||
+ Add support for comments in `.dockerignore` [#23111](https://github.com/docker/docker/pull/23111)
|
||||
* Support for UTF-8 in Dockerfiles [#23372](https://github.com/docker/docker/pull/23372)
|
||||
* Skip UTF-8 BOM bytes from `Dockerfile` and `.dockerignore` if exist [#23234](https://github.com/docker/docker/pull/23234)
|
||||
* Windows: support for `ARG` to match Linux [#22508](https://github.com/docker/docker/pull/22508)
|
||||
- Fix error message when building using a daemon with the bridge network disabled [#22932](https://github.com/docker/docker/pull/22932)
|
||||
|
||||
### Contrib
|
||||
|
||||
* Enable seccomp for Centos 7 and Oracle Linux 7 [#22344](https://github.com/docker/docker/pull/22344)
|
||||
- Remove MountFlags in systemd unit to allow shared mount propagation [#22806](https://github.com/docker/docker/pull/22806)
|
||||
|
||||
### Distribution
|
||||
|
||||
+ Add `--max-concurrent-downloads` and `--max-concurrent-uploads` daemon flags useful for situations where network connections don't support multiple downloads/uploads [#22445](https://github.com/docker/docker/pull/22445)
|
||||
* Registry operations now honor the `ALL_PROXY` environment variable [#22316](https://github.com/docker/docker/pull/22316)
|
||||
* Provide more information to the user on `docker load` [#23377](https://github.com/docker/docker/pull/23377)
|
||||
|
||||
### Logging
|
||||
|
||||
+ Syslog logging driver now supports DGRAM sockets [#21613](https://github.com/docker/docker/pull/21613)
|
||||
+ Add `--details` option to `docker logs` to also display log tags [#21889](https://github.com/docker/docker/pull/21889)
|
||||
+ Enable syslog logger to have access to env and labels [#21724](https://github.com/docker/docker/pull/21724)
|
||||
+ An additional syslog-format option `rfc5424micro` to allow microsecond resolution in syslog timestamp [#21844](https://github.com/docker/docker/pull/21844)
|
||||
* Inherit the daemon log options when creating containers [#21153](https://github.com/docker/docker/pull/21153)
|
||||
* Remove `docker/` prefix from log messages tag and replace it with `{{.DaemonName}}` so that users have the option of changing the prefix [#22384](https://github.com/docker/docker/pull/22384)
|
||||
|
||||
### Networking
|
||||
|
||||
+ Built-in Virtual-IP based internal and ingress load-balancing using IPVS [#23361](https://github.com/docker/docker/pull/23361)
|
||||
+ Secured multi-host overlay networking using encrypted control-plane and Data-plane [#23361](https://github.com/docker/docker/pull/23361)
|
||||
+ MacVlan driver is out of experimental [#23524](https://github.com/docker/docker/pull/23524)
|
||||
+ Add `driver` filter to `network ls` [#22319](https://github.com/docker/docker/pull/22319)
|
||||
+ Adding `network` filter to `docker ps --filter` [#23300](https://github.com/docker/docker/pull/23300)
|
||||
+ Add `--link-local-ip` flag to `create`, `run` and `network connect` to specify a container's link-local address [#23415](https://github.com/docker/docker/pull/23415)
|
||||
+ Add network label filter support [#21495](https://github.com/docker/docker/pull/21495)
|
||||
* Removed dependency on external KV-Store for Overlay networking in Swarm-Mode [#23361](https://github.com/docker/docker/pull/23361)
|
||||
* Add container's short-id as default network alias [#21901](https://github.com/docker/docker/pull/21901)
|
||||
* `run` options `--dns` and `--net=host` are no longer mutually exclusive [#22408](https://github.com/docker/docker/pull/22408)
|
||||
- Fix DNS issue when renaming containers with generated names [#22716](https://github.com/docker/docker/pull/22716)
|
||||
- Allow both `network inspect -f {{.Id}}` and `network inspect -f {{.ID}}` to address inconsistency with inspect output [#23226](https://github.com/docker/docker/pull/23226)
|
||||
|
||||
### Plugins (experimental)
|
||||
|
||||
+ New `plugin` command to manager plugins with `install`, `enable`, `disable`, `rm`, `inspect`, `set` subcommands [#23446](https://github.com/docker/docker/pull/23446)
|
||||
|
||||
### Remote API (v1.24) & Client
|
||||
|
||||
+ Split the binary into two: `docker` (client) and `dockerd` (daemon) [#20639](https://github.com/docker/docker/pull/20639)
|
||||
+ Add `before` and `since` filters to `docker images --filter` [#22908](https://github.com/docker/docker/pull/22908)
|
||||
+ Add `--limit` option to `docker search` [#23107](https://github.com/docker/docker/pull/23107)
|
||||
+ Add `--filter` option to `docker search` [#22369](https://github.com/docker/docker/pull/22369)
|
||||
+ Add security options to `docker info` output [#21172](https://github.com/docker/docker/pull/21172) [#23520](https://github.com/docker/docker/pull/23520)
|
||||
+ Add insecure registries to `docker info` output [#20410](https://github.com/docker/docker/pull/20410)
|
||||
+ Extend Docker authorization with TLS user information [#21556](https://github.com/docker/docker/pull/21556)
|
||||
+ devicemapper: expose Mininum Thin Pool Free Space through `docker info` [#21945](https://github.com/docker/docker/pull/21945)
|
||||
* API now returns a JSON object when an error occurs making it more consistent [#22880](https://github.com/docker/docker/pull/22880)
|
||||
- Prevent `docker run -i --restart` from hanging on exit [#22777](https://github.com/docker/docker/pull/22777)
|
||||
- Fix API/CLI discrepancy on hostname validation [#21641](https://github.com/docker/docker/pull/21641)
|
||||
- Fix discrepancy in the format of sizes in `stats` from HumanSize to BytesSize [#21773](https://github.com/docker/docker/pull/21773)
|
||||
- authz: when request is denied return forbbiden exit code (403) [#22448](https://github.com/docker/docker/pull/22448)
|
||||
|
||||
### Runtime
|
||||
|
||||
+ Add `--live-restore` daemon flag to keep containers running when daemon shuts down, and regain control on startup [#23213](https://github.com/docker/docker/pull/23213)
|
||||
+ Ability to add OCI-compatible runtimes (via `--add-runtime` daemon flag) and select one with `--runtime` on `create` and `run` [#22983](https://github.com/docker/docker/pull/22983)
|
||||
+ New `overlay2` graphdriver for Linux 4.0+ with multiple lower directory support [#22126](https://github.com/docker/docker/pull/22126)
|
||||
+ New load/save image events [#22137](https://github.com/docker/docker/pull/22137)
|
||||
+ Add support for reloading daemon configuration through systemd [#22446](https://github.com/docker/docker/pull/22446)
|
||||
+ Add disk quota support for btrfs [#19651](https://github.com/docker/docker/pull/19651)
|
||||
+ Add disk quota support for zfs [#21946](https://github.com/docker/docker/pull/21946)
|
||||
+ Add support for `docker run --pid=container:<id>` [#22481](https://github.com/docker/docker/pull/22481)
|
||||
+ Align default seccomp profile with selected capabilities [#22554](https://github.com/docker/docker/pull/22554)
|
||||
+ Add a `daemon reload` event when the daemon reloads its configuration [#22590](https://github.com/docker/docker/pull/22590)
|
||||
+ Add `trace` capability in the pprof profiler to show execution traces in binary form [#22715](https://github.com/docker/docker/pull/22715)
|
||||
+ Add a `detach` event [#22898](https://github.com/docker/docker/pull/22898)
|
||||
+ Add support for setting sysctls with `--sysctl` [#19265](https://github.com/docker/docker/pull/19265)
|
||||
+ Add `--storage-opt` flag to `create` and `run` allowing to set `size` on devicemapper [#19367](https://github.com/docker/docker/pull/19367)
|
||||
* Undeprecate the `-c` short alias of `--cpu-shares` on `run`, `build`, `create`, `update` [#22621](https://github.com/docker/docker/pull/22621)
|
||||
* Prevent from using aufs and overlay graphdrivers on an eCryptfs mount [#23121](https://github.com/docker/docker/pull/23121)
|
||||
- Fix issues with tmpfs mount ordering [#22329](https://github.com/docker/docker/pull/22329)
|
||||
- Created containers are no longer listed on `docker ps -a -f exited=0` [#21947](https://github.com/docker/docker/pull/21947)
|
||||
- Fix an issue where containers are stuck in a "Removal In Progress" state [#22423](https://github.com/docker/docker/pull/22423)
|
||||
- Fix bug that was returning an HTTP 500 instead of a 400 when not specifying a command on run/create [#22762](https://github.com/docker/docker/pull/22762)
|
||||
- Fix bug with `--detach-keys` whereby input matching a prefix of the detach key was not preserved [#22943](https://github.com/docker/docker/pull/22943)
|
||||
- SELinux labeling is now disabled when using `--privileged` mode [#22993](https://github.com/docker/docker/pull/22993)
|
||||
- If volume-mounted into a container, `/etc/hosts`, `/etc/resolv.conf`, `/etc/hostname` are no longer SELinux-relabeled [#22993](https://github.com/docker/docker/pull/22993)
|
||||
- Fix inconsistency in `--tmpfs` behavior regarding mount options [#22438](https://github.com/docker/docker/pull/22438)
|
||||
- Fix an issue where daemon hangs at startup [#23148](https://github.com/docker/docker/pull/23148)
|
||||
- Ignore SIGPIPE events to prevent journald restarts to crash docker in some cases [#22460](https://github.com/docker/docker/pull/22460)
|
||||
- Containers are not removed from stats list on error [#20835](https://github.com/docker/docker/pull/20835)
|
||||
- Fix `on-failure` restart policy when daemon restarts [#20853](https://github.com/docker/docker/pull/20853)
|
||||
- Fix an issue with `stats` when a container is using another container's network [#21904](https://github.com/docker/docker/pull/21904)
|
||||
|
||||
### Swarm Mode
|
||||
|
||||
+ New `swarm` command to manage swarms with `init`, `join`, `leave`, `update` subcommands [#23361](https://github.com/docker/docker/pull/23361)
|
||||
+ New `service` command to manage swarm-wide services with `create`, `inspect`, `update`, `remove`, `tasks` subcommands [#23361](https://github.com/docker/docker/pull/23361)
|
||||
+ New `node` command to manage nodes with `accept`, `promote`, `demote`, `inspect`, `update`, `tasks`, `ls` and `rm` subcommands [#23361](https://github.com/docker/docker/pull/23361)
|
||||
+ (experimental) New `stack` and `deploy` commands to manage and deploy multi-service applications [#23522](https://github.com/docker/docker/pull/23522)
|
||||
|
||||
### Volume
|
||||
|
||||
+ Add support for local and global volume scopes (analogous to network scopes) [#22077](https://github.com/docker/docker/pull/22077)
|
||||
+ Allow volume drivers to provide a `Status` field [#21006](https://github.com/docker/docker/pull/21006)
|
||||
+ Add name/driver filter support for volume [#21361](https://github.com/docker/docker/pull/21361)
|
||||
* Mount/Unmount operations now receives an opaque ID to allow volume drivers to differentiate between two callers [#21015](https://github.com/docker/docker/pull/21015)
|
||||
- Fix issue preventing to remove a volume in a corner case [#22103](https://github.com/docker/docker/pull/22103)
|
||||
- Windows: Enable auto-creation of host-path to match Linux [#22094](https://github.com/docker/docker/pull/22094)
|
||||
|
||||
|
||||
### DEPRECATION
|
||||
* Environment variables `DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE` and `DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE` have been renamed
|
||||
to `DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE` and `DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE` respectively [#22574](https://github.com/docker/docker/pull/22574)
|
||||
* Remove deprecated `syslog-tag`, `gelf-tag`, `fluentd-tag` log option in favor of the more generic `tag` one [#22620](https://github.com/docker/docker/pull/22620)
|
||||
* Remove deprecated feature of passing HostConfig at API container start [#22570](https://github.com/docker/docker/pull/22570)
|
||||
* Remove deprecated `-f`/`--force` flag on docker tag [#23090](https://github.com/docker/docker/pull/23090)
|
||||
* Remove deprecated `/containers/<id|name>/copy` endpoint [#22149](https://github.com/docker/docker/pull/22149)
|
||||
* Remove deprecated `docker ps` flags `--since` and `--before` [#22138](https://github.com/docker/docker/pull/22138)
|
||||
* Deprecate the old 3-args form of `docker import` [#23273](https://github.com/docker/docker/pull/23273)
|
||||
|
||||
## 1.11.2 (2016-05-31)
|
||||
|
||||
### Networking
|
||||
@@ -116,7 +243,7 @@ be found.
|
||||
|
||||
### Misc
|
||||
|
||||
+ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/c))
|
||||
+ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/21385))
|
||||
+ Support for building the Docker cli for OpenBSD was added ([#21325](https://github.com/docker/docker/pull/21325))
|
||||
+ Labels can now be applied at network, volume and image creation ([#21270](https://github.com/docker/docker/pull/21270))
|
||||
* The `dockremap` is now created as a system user ([#21266](https://github.com/docker/docker/pull/21266))
|
||||
@@ -891,7 +1018,7 @@ by another client (#15489)
|
||||
#### Security
|
||||
- Fix tar breakout vulnerability
|
||||
* Extractions are now sandboxed chroot
|
||||
- Security options are no longer committed to images
|
||||
- Security options are no longer comitted to images
|
||||
|
||||
#### Runtime
|
||||
- Fix deadlock in `docker ps -f exited=1`
|
||||
@@ -1317,7 +1444,7 @@ by another client (#15489)
|
||||
* Update issue filing instructions
|
||||
* Warn against the use of symlinks for Docker's storage folder
|
||||
* Replace the Firefox example with an IceWeasel example
|
||||
* Rewrite the PostgresSQL example using a Dockerfile and add more details to it
|
||||
* Rewrite the PostgreSQL example using a Dockerfile and add more details to it
|
||||
* Improve the OS X documentation
|
||||
|
||||
#### Remote API
|
||||
|
||||
@@ -217,7 +217,7 @@ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.0.5 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||
@@ -233,10 +233,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -244,7 +244,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -36,6 +36,7 @@ RUN apt-get update && apt-get install -y \
|
||||
libapparmor-dev \
|
||||
libc6-dev \
|
||||
libcap-dev \
|
||||
libltdl-dev \
|
||||
libsqlite3-dev \
|
||||
libsystemd-dev \
|
||||
mercurial \
|
||||
@@ -164,7 +165,7 @@ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.0.5 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||
@@ -180,10 +181,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -191,7 +192,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -173,7 +173,7 @@ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.0.5 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||
@@ -189,10 +189,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -200,7 +200,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -74,10 +74,10 @@ WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -85,7 +85,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -188,7 +188,7 @@ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.0.5 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||
@@ -204,10 +204,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="apparmor seccomp selinux" \
|
||||
@@ -215,7 +215,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -161,7 +161,7 @@ RUN useradd --create-home --gid docker unprivilegeduser
|
||||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux seccomp
|
||||
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv $PWD/.bashrc ~/.bashrc
|
||||
@@ -181,7 +181,7 @@ RUN ./contrib/download-frozen-image-v2.sh /docker-frozen-images \
|
||||
# Download man page generator
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone --depth 1 -b v1.0.4 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.0.5 https://github.com/cpuguy83/go-md2man.git "$GOPATH/src/github.com/cpuguy83/go-md2man" \
|
||||
&& git clone --depth 1 -b v1.4 https://github.com/russross/blackfriday.git "$GOPATH/src/github.com/russross/blackfriday" \
|
||||
&& go get -v -d github.com/cpuguy83/go-md2man \
|
||||
&& go build -v -o /usr/local/bin/go-md2man github.com/cpuguy83/go-md2man \
|
||||
@@ -197,10 +197,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -208,7 +208,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -57,10 +57,10 @@ ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
||||
ENV CGO_LDFLAGS -L/lib
|
||||
|
||||
# Install runc
|
||||
ENV RUNC_COMMIT 5ce88a95f6cf218ba7f3309562f95464a968e890
|
||||
ENV RUNC_COMMIT cc29e3dded8e27ba8f65738f40d251c885030a28
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/crosbymichael/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
|
||||
&& git checkout -q "$RUNC_COMMIT" \
|
||||
&& make static BUILDTAGS="seccomp apparmor selinux" \
|
||||
@@ -68,7 +68,7 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install containerd
|
||||
ENV CONTAINERD_COMMIT 860f3a94940894ac0a106eff4bd1616a67407ee2
|
||||
ENV CONTAINERD_COMMIT 1b3a81545ca79456086dc2aa424357be98b962ee
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
|
||||
|
||||
@@ -4,8 +4,8 @@ package bundlefile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Bundlefile stores the contents of a bundlefile
|
||||
@@ -34,19 +34,28 @@ type Port struct {
|
||||
}
|
||||
|
||||
// LoadFile loads a bundlefile from a path to the file
|
||||
func LoadFile(path string) (*Bundlefile, error) {
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func LoadFile(reader io.Reader) (*Bundlefile, error) {
|
||||
bundlefile := &Bundlefile{}
|
||||
|
||||
if err := json.NewDecoder(reader).Decode(bundlefile); err != nil {
|
||||
decoder := json.NewDecoder(reader)
|
||||
if err := decoder.Decode(bundlefile); err != nil {
|
||||
switch jsonErr := err.(type) {
|
||||
case *json.SyntaxError:
|
||||
return nil, fmt.Errorf(
|
||||
"JSON syntax error at byte %v: %s",
|
||||
jsonErr.Offset,
|
||||
jsonErr.Error())
|
||||
case *json.UnmarshalTypeError:
|
||||
return nil, fmt.Errorf(
|
||||
"Unexpected type at byte %v. Expected %s but received %s.",
|
||||
jsonErr.Offset,
|
||||
jsonErr.Type,
|
||||
jsonErr.Value)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bundlefile, err
|
||||
return bundlefile, nil
|
||||
}
|
||||
|
||||
// Print writes the contents of the bundlefile to the output writer
|
||||
|
||||
79
api/client/bundlefile/bundlefile_test.go
Normal file
79
api/client/bundlefile/bundlefile_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// +build experimental
|
||||
|
||||
package bundlefile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestLoadFileV01Success(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": {
|
||||
"redis": {
|
||||
"Image": "redis@sha256:4b24131101fa0117bcaa18ac37055fffd9176aa1a240392bb8ea85e0be50f2ce",
|
||||
"Networks": ["default"]
|
||||
},
|
||||
"web": {
|
||||
"Image": "dockercloud/hello-world@sha256:fe79a2cfbd17eefc344fb8419420808df95a1e22d93b7f621a7399fd1e9dca1d",
|
||||
"Networks": ["default"],
|
||||
"User": "web"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
bundle, err := LoadFile(reader)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, bundle.Version, "0.1")
|
||||
assert.Equal(t, len(bundle.Services), 2)
|
||||
}
|
||||
|
||||
func TestLoadFileSyntaxError(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": unquoted string
|
||||
}`)
|
||||
|
||||
_, err := LoadFile(reader)
|
||||
assert.Error(t, err, "syntax error at byte 37: invalid character 'u'")
|
||||
}
|
||||
|
||||
func TestLoadFileTypeError(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": {
|
||||
"web": {
|
||||
"Image": "redis",
|
||||
"Networks": "none"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
_, err := LoadFile(reader)
|
||||
assert.Error(t, err, "Unexpected type at byte 94. Expected []string but received string")
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
bundle := &Bundlefile{
|
||||
Version: "0.1",
|
||||
Services: map[string]Service{
|
||||
"web": {
|
||||
Image: "image",
|
||||
Command: []string{"echo", "something"},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NilError(t, Print(&buffer, bundle))
|
||||
output := buffer.String()
|
||||
assert.Contains(t, output, "\"Image\": \"image\"")
|
||||
assert.Contains(t, output,
|
||||
`"Command": [
|
||||
"echo",
|
||||
"something"
|
||||
]`)
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func NewCopyCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH`,
|
||||
Short: "Copy files/folders between a container and the local filesystem",
|
||||
Long: strings.Join([]string{
|
||||
"Copy files/folders between a container and the local filesystem\n",
|
||||
"\nUse '-' as the source to read a tar archive from stdin\n",
|
||||
"and extract it to a directory destination in a container.\n",
|
||||
"Use '-' as the destination to stream a tar archive of a\n",
|
||||
|
||||
@@ -15,7 +15,7 @@ type diffOptions struct {
|
||||
container string
|
||||
}
|
||||
|
||||
// NewDiffCommand creats a new cobra.Command for `docker diff`
|
||||
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
||||
func NewDiffCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts diffOptions
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ func NewPsCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||
flags.BoolVarP(&opts.nLatest, "latest", "l", false, "Show the latest created container (includes all states)")
|
||||
flags.IntVarP(&opts.last, "", "n", -1, "Show n last created containers (includes all states)")
|
||||
flags.IntVarP(&opts.last, "last", "n", -1, "Show n last created containers (includes all states)")
|
||||
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print containers using a Go template")
|
||||
flags.StringSliceVarP(&opts.filter, "filter", "f", []string{}, "Filter output based on conditions provided")
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ type restartOptions struct {
|
||||
containers []string
|
||||
}
|
||||
|
||||
// NewRestartCommand creats a new cobra.Command for `docker restart`
|
||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
||||
func NewRestartCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts restartOptions
|
||||
|
||||
@@ -41,7 +41,8 @@ func runRestart(dockerCli *client.DockerCli, opts *restartOptions) error {
|
||||
ctx := context.Background()
|
||||
var errs []string
|
||||
for _, name := range opts.containers {
|
||||
if err := dockerCli.Client().ContainerRestart(ctx, name, time.Duration(opts.nSeconds)*time.Second); err != nil {
|
||||
timeout := time.Duration(opts.nSeconds) * time.Second
|
||||
if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
||||
|
||||
@@ -20,7 +20,7 @@ type rmOptions struct {
|
||||
containers []string
|
||||
}
|
||||
|
||||
// NewRmCommand creats a new cobra.Command for `docker rm`
|
||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
||||
func NewRmCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts rmOptions
|
||||
|
||||
|
||||
@@ -63,8 +63,9 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// We always use c.ID instead of container to maintain consistency during `docker start`
|
||||
if !c.Config.Tty {
|
||||
sigc := dockerCli.ForwardAllSignals(ctx, container)
|
||||
sigc := dockerCli.ForwardAllSignals(ctx, c.ID)
|
||||
defer signal.StopCatch(sigc)
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||
in = dockerCli.In()
|
||||
}
|
||||
|
||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, container, options)
|
||||
resp, errAttach := dockerCli.Client().ContainerAttach(ctx, c.ID, options)
|
||||
if errAttach != nil && errAttach != httputil.ErrPersistEOF {
|
||||
// ContainerAttach return an ErrPersistEOF (connection closed)
|
||||
// means server met an error and put it in Hijacked connection
|
||||
@@ -103,7 +104,7 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||
})
|
||||
|
||||
// 3. Start the container.
|
||||
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
|
||||
if err := dockerCli.Client().ContainerStart(ctx, c.ID, types.ContainerStartOptions{}); err != nil {
|
||||
cancelFun()
|
||||
<-cErr
|
||||
return err
|
||||
@@ -111,14 +112,14 @@ func runStart(dockerCli *client.DockerCli, opts *startOptions) error {
|
||||
|
||||
// 4. Wait for attachment to break.
|
||||
if c.Config.Tty && dockerCli.IsTerminalOut() {
|
||||
if err := dockerCli.MonitorTtySize(ctx, container, false); err != nil {
|
||||
if err := dockerCli.MonitorTtySize(ctx, c.ID, false); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "Error monitoring TTY size: %s\n", err)
|
||||
}
|
||||
}
|
||||
if attchErr := <-cErr; attchErr != nil {
|
||||
return attchErr
|
||||
}
|
||||
_, status, err := getExitCode(dockerCli, ctx, container)
|
||||
_, status, err := getExitCode(dockerCli, ctx, c.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ func runStop(dockerCli *client.DockerCli, opts *stopOptions) error {
|
||||
|
||||
var errs []string
|
||||
for _, container := range opts.containers {
|
||||
if err := dockerCli.Client().ContainerStop(ctx, container, time.Duration(opts.time)*time.Second); err != nil {
|
||||
timeout := time.Duration(opts.time) * time.Second
|
||||
if err := dockerCli.Client().ContainerStop(ctx, container, &timeout); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
||||
|
||||
@@ -18,7 +18,7 @@ type topOptions struct {
|
||||
args []string
|
||||
}
|
||||
|
||||
// NewTopCommand creats a new cobra.Command for `docker top`
|
||||
// NewTopCommand creates a new cobra.Command for `docker top`
|
||||
func NewTopCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts topOptions
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ type waitOptions struct {
|
||||
containers []string
|
||||
}
|
||||
|
||||
// NewWaitCommand creats a new cobra.Command for `docker wait`
|
||||
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
||||
func NewWaitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts waitOptions
|
||||
|
||||
|
||||
@@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() {
|
||||
ctx.postformat(tmpl, &containerContext{})
|
||||
}
|
||||
|
||||
func isDangling(image types.Image) bool {
|
||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
||||
}
|
||||
|
||||
func (ctx ImageContext) Write() {
|
||||
switch ctx.Format {
|
||||
case tableFormatKey:
|
||||
@@ -200,42 +204,98 @@ virtual_size: {{.Size}}
|
||||
}
|
||||
|
||||
for _, image := range ctx.Images {
|
||||
images := []*imageContext{}
|
||||
if isDangling(image) {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: "<none>",
|
||||
tag: "<none>",
|
||||
digest: "<none>",
|
||||
})
|
||||
} else {
|
||||
repoTags := map[string][]string{}
|
||||
repoDigests := map[string][]string{}
|
||||
|
||||
repoTags := image.RepoTags
|
||||
repoDigests := image.RepoDigests
|
||||
|
||||
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
||||
// dangling image - clear out either repoTags or repoDigests so we only show it once below
|
||||
repoDigests = []string{}
|
||||
}
|
||||
// combine the tags and digests lists
|
||||
tagsAndDigests := append(repoTags, repoDigests...)
|
||||
for _, repoAndRef := range tagsAndDigests {
|
||||
repo := "<none>"
|
||||
tag := "<none>"
|
||||
digest := "<none>"
|
||||
|
||||
if !strings.HasPrefix(repoAndRef, "<none>") {
|
||||
ref, err := reference.ParseNamed(repoAndRef)
|
||||
for _, refString := range append(image.RepoTags) {
|
||||
ref, err := reference.ParseNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
repo = ref.Name()
|
||||
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical:
|
||||
digest = x.Digest().String()
|
||||
case reference.NamedTagged:
|
||||
tag = x.Tag()
|
||||
if nt, ok := ref.(reference.NamedTagged); ok {
|
||||
repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
|
||||
}
|
||||
}
|
||||
imageCtx := &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: digest,
|
||||
for _, refString := range append(image.RepoDigests) {
|
||||
ref, err := reference.ParseNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if c, ok := ref.(reference.Canonical); ok {
|
||||
repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
|
||||
}
|
||||
}
|
||||
|
||||
for repo, tags := range repoTags {
|
||||
digests := repoDigests[repo]
|
||||
|
||||
// Do not display digests as their own row
|
||||
delete(repoDigests, repo)
|
||||
|
||||
if !ctx.Digest {
|
||||
// Ignore digest references, just show tag once
|
||||
digests = nil
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if len(digests) == 0 {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: "<none>",
|
||||
})
|
||||
continue
|
||||
}
|
||||
// Display the digests for each tag
|
||||
for _, dgst := range digests {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: dgst,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Show rows for remaining digest only references
|
||||
for repo, digests := range repoDigests {
|
||||
// If digests are displayed, show row per digest
|
||||
if ctx.Digest {
|
||||
for _, dgst := range digests {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: "<none>",
|
||||
digest: dgst,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: "<none>",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, imageCtx := range images {
|
||||
err = ctx.contextFormat(tmpl, imageCtx)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -301,7 +301,6 @@ func TestImageContextWrite(t *testing.T) {
|
||||
},
|
||||
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
image tag1 imageID1 24 hours ago 0 B
|
||||
image <none> imageID1 24 hours ago 0 B
|
||||
image tag2 imageID2 24 hours ago 0 B
|
||||
<none> <none> imageID3 24 hours ago 0 B
|
||||
`,
|
||||
@@ -312,7 +311,7 @@ image tag2 imageID2 24 hours ago
|
||||
Format: "table {{.Repository}}",
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -322,7 +321,6 @@ image tag2 imageID2 24 hours ago
|
||||
Digest: true,
|
||||
},
|
||||
`REPOSITORY DIGEST
|
||||
image <none>
|
||||
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
||||
image <none>
|
||||
<none> <none>
|
||||
@@ -335,7 +333,7 @@ image <none>
|
||||
Quiet: true,
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -344,7 +342,7 @@ image <none>
|
||||
Quiet: true,
|
||||
},
|
||||
},
|
||||
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
||||
"imageID1\nimageID2\nimageID3\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -355,8 +353,7 @@ image <none>
|
||||
Digest: true,
|
||||
},
|
||||
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
|
||||
image tag1 <none> imageID1 24 hours ago 0 B
|
||||
image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
||||
image tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
||||
image tag2 <none> imageID2 24 hours ago 0 B
|
||||
<none> <none> <none> imageID3 24 hours ago 0 B
|
||||
`,
|
||||
@@ -369,7 +366,7 @@ image tag2 <none>
|
||||
},
|
||||
Digest: true,
|
||||
},
|
||||
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
||||
"imageID1\nimageID2\nimageID3\n",
|
||||
},
|
||||
// Raw Format
|
||||
{
|
||||
@@ -384,12 +381,6 @@ image_id: imageID1
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: <none>
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: tag2
|
||||
image_id: imageID2
|
||||
@@ -402,7 +393,7 @@ image_id: imageID3
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
||||
`, expectedTime, expectedTime, expectedTime),
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -413,13 +404,6 @@ virtual_size: 0 B
|
||||
},
|
||||
fmt.Sprintf(`repository: image
|
||||
tag: tag1
|
||||
digest: <none>
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: <none>
|
||||
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
@@ -439,7 +423,7 @@ image_id: imageID3
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
||||
`, expectedTime, expectedTime, expectedTime),
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -449,7 +433,6 @@ virtual_size: 0 B
|
||||
},
|
||||
},
|
||||
`image_id: imageID1
|
||||
image_id: imageID1
|
||||
image_id: imageID2
|
||||
image_id: imageID3
|
||||
`,
|
||||
@@ -461,7 +444,7 @@ image_id: imageID3
|
||||
Format: "{{.Repository}}",
|
||||
},
|
||||
},
|
||||
"image\nimage\nimage\n<none>\n",
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
@@ -470,7 +453,7 @@ image_id: imageID3
|
||||
},
|
||||
Digest: true,
|
||||
},
|
||||
"image\nimage\nimage\n<none>\n",
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func New(client client.APIClient, noResolve bool) *IDResolver {
|
||||
func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string, error) {
|
||||
switch t.(type) {
|
||||
case swarm.Node:
|
||||
node, err := r.client.NodeInspect(ctx, id)
|
||||
node, _, err := r.client.NodeInspectWithRaw(ctx, id)
|
||||
if err != nil {
|
||||
return id, nil
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string,
|
||||
}
|
||||
return id, nil
|
||||
case swarm.Service:
|
||||
service, err := r.client.ServiceInspect(ctx, id)
|
||||
service, _, err := r.client.ServiceInspectWithRaw(ctx, id)
|
||||
if err != nil {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func NewBuildCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build PATH | URL | -",
|
||||
Use: "build [OPTIONS] PATH | URL | -",
|
||||
Short: "Build an image from a Dockerfile",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// NewPushCommand creates a new `docker push` command
|
||||
func NewPushCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push NAME[:TAG]",
|
||||
Use: "push [OPTIONS] NAME[:TAG]",
|
||||
Short: "Push an image or a repository to a registry",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewTagCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts tagOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "tag IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]",
|
||||
Use: "tag IMAGE[:TAG] IMAGE[:TAG]",
|
||||
Short: "Tag an image into a repository",
|
||||
Args: cli.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -41,7 +41,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
|
||||
// print a warning if devicemapper is using a loopback file
|
||||
if pair[0] == "Data loop file" {
|
||||
fmt.Fprintln(cli.err, " WARNING: Usage of loopback devices is strongly discouraged for production use. Either use `--storage-opt dm.thinpooldev` or use `--storage-opt dm.no_warn_on_loop_devices=true` to suppress this warning.")
|
||||
fmt.Fprintln(cli.err, " WARNING: Usage of loopback devices is strongly discouraged for production use. Use `--storage-opt dm.thinpooldev` to specify a custom block storage device.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
fmt.Fprintf(cli.out, "%s: %s\n", pair[0], pair[1])
|
||||
}
|
||||
}
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Execution Driver: %s\n", info.ExecutionDriver)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Logging Driver: %s\n", info.LoggingDriver)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Cgroup Driver: %s\n", info.CgroupDriver)
|
||||
|
||||
@@ -94,6 +93,10 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
fmt.Fprintf(cli.out, "Default Runtime: %s\n", info.DefaultRuntime)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "Security Options:")
|
||||
ioutils.FprintfIfNotEmpty(cli.out, " %s", strings.Join(info.SecurityOptions, " "))
|
||||
fmt.Fprintf(cli.out, "\n")
|
||||
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)
|
||||
|
||||
@@ -86,10 +86,10 @@ func (cli *DockerCli) inspectAll(ctx context.Context, getSize bool) inspect.GetR
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
return i, rawImage, err
|
||||
return i, rawImage, nil
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
return c, rawContainer, err
|
||||
return c, rawContainer, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func newConnectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "connect [OPTIONS] NETWORK CONTAINER",
|
||||
Short: "Connects a container to a network",
|
||||
Short: "Connect a container to a network",
|
||||
Args: cli.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.network = args[0]
|
||||
|
||||
@@ -40,7 +40,7 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create",
|
||||
Use: "create [OPTIONS] NETWORK",
|
||||
Short: "Create a network",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
@@ -19,7 +19,7 @@ func newDisconnectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "disconnect [OPTIONS] NETWORK CONTAINER",
|
||||
Short: "Disconnects container from a network",
|
||||
Short: "Disconnect a container from a network",
|
||||
Args: cli.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.network = args[0]
|
||||
|
||||
@@ -19,7 +19,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] NETWORK [NETWORK...]",
|
||||
Short: "Displays detailed information on one or more networks",
|
||||
Short: "Display detailed information on one or more networks",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.names = args
|
||||
|
||||
@@ -33,7 +33,7 @@ func runRemove(dockerCli *client.DockerCli, networks []string) error {
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Err(), "%s\n", name)
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
|
||||
@@ -7,34 +7,25 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newAcceptCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var flags *pflag.FlagSet
|
||||
|
||||
cmd := &cobra.Command{
|
||||
return &cobra.Command{
|
||||
Use: "accept NODE [NODE...]",
|
||||
Short: "Accept a node in the swarm",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runAccept(dockerCli, flags, args)
|
||||
return runAccept(dockerCli, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
|
||||
for _, id := range args {
|
||||
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
|
||||
node.Spec.Membership = swarm.NodeMembershipAccepted
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to accept a node in the swarm.")
|
||||
func runAccept(dockerCli *client.DockerCli, nodes []string) error {
|
||||
accept := func(node *swarm.Node) {
|
||||
node.Spec.Membership = swarm.NodeMembershipAccepted
|
||||
}
|
||||
|
||||
return nil
|
||||
success := func(nodeID string) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", nodeID)
|
||||
}
|
||||
return updateNodes(dockerCli, nodes, accept, success)
|
||||
}
|
||||
|
||||
@@ -35,9 +35,10 @@ func NewNodeCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func nodeReference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
|
||||
// The special value "self" for a node reference is mapped to the current
|
||||
// node, hence the node ID is retrieved using the `/info` endpoint.
|
||||
// Reference returns the reference of a node. The special value "self" for a node
|
||||
// reference is mapped to the current node, hence the node ID is retrieved using
|
||||
// the `/info` endpoint.
|
||||
func Reference(client apiclient.APIClient, ctx context.Context, ref string) (string, error) {
|
||||
if ref == "self" {
|
||||
info, err := client.Info(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -7,34 +7,25 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newDemoteCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var flags *pflag.FlagSet
|
||||
|
||||
cmd := &cobra.Command{
|
||||
return &cobra.Command{
|
||||
Use: "demote NODE [NODE...]",
|
||||
Short: "Demote a node from manager in the swarm",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDemote(dockerCli, flags, args)
|
||||
return runDemote(dockerCli, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
|
||||
for _, id := range args {
|
||||
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
|
||||
node.Spec.Role = swarm.NodeRoleWorker
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to demote a manager in the swarm.")
|
||||
func runDemote(dockerCli *client.DockerCli, nodes []string) error {
|
||||
demote := func(node *swarm.Node) {
|
||||
node.Spec.Role = swarm.NodeRoleWorker
|
||||
}
|
||||
|
||||
return nil
|
||||
success := func(nodeID string) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", nodeID)
|
||||
}
|
||||
return updateNodes(dockerCli, nodes, demote, success)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] self|NODE [NODE...]",
|
||||
Short: "Inspect a node in the swarm",
|
||||
Short: "Display detailed information on one or more nodes",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.nodeIds = args
|
||||
@@ -45,11 +45,11 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
nodeRef, err := nodeReference(client, ctx, ref)
|
||||
nodeRef, err := Reference(client, ctx, ref)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
node, err := client.NodeInspect(ctx, nodeRef)
|
||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeRef)
|
||||
return node, nil, err
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func printNode(out io.Writer, node swarm.Node) {
|
||||
if node.ManagerStatus != nil {
|
||||
fmt.Fprintln(out, "Manager Status:")
|
||||
fmt.Fprintf(out, " Address:\t\t%s\n", node.ManagerStatus.Addr)
|
||||
fmt.Fprintf(out, " Raft status:\t\t%s\n", client.PrettyPrint(node.ManagerStatus.Reachability))
|
||||
fmt.Fprintf(out, " Raft Status:\t\t%s\n", client.PrettyPrint(node.ManagerStatus.Reachability))
|
||||
leader := "No"
|
||||
if node.ManagerStatus.Leader {
|
||||
leader = "Yes"
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\n"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
@@ -74,24 +74,19 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
||||
// Ignore flushing errors
|
||||
defer writer.Flush()
|
||||
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS", "LEADER")
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "HOSTNAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS")
|
||||
for _, node := range nodes {
|
||||
name := node.Spec.Name
|
||||
name := node.Description.Hostname
|
||||
availability := string(node.Spec.Availability)
|
||||
membership := string(node.Spec.Membership)
|
||||
|
||||
if name == "" {
|
||||
name = node.Description.Hostname
|
||||
}
|
||||
|
||||
leader := ""
|
||||
if node.ManagerStatus != nil && node.ManagerStatus.Leader {
|
||||
leader = "Yes"
|
||||
}
|
||||
|
||||
reachability := ""
|
||||
if node.ManagerStatus != nil {
|
||||
reachability = string(node.ManagerStatus.Reachability)
|
||||
if node.ManagerStatus.Leader {
|
||||
reachability = "Leader"
|
||||
} else {
|
||||
reachability = string(node.ManagerStatus.Reachability)
|
||||
}
|
||||
}
|
||||
|
||||
ID := node.ID
|
||||
@@ -107,8 +102,7 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
||||
client.PrettyPrint(membership),
|
||||
client.PrettyPrint(string(node.Status.State)),
|
||||
client.PrettyPrint(availability),
|
||||
client.PrettyPrint(reachability),
|
||||
leader)
|
||||
client.PrettyPrint(reachability))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,34 +7,25 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func newPromoteCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var flags *pflag.FlagSet
|
||||
|
||||
cmd := &cobra.Command{
|
||||
return &cobra.Command{
|
||||
Use: "promote NODE [NODE...]",
|
||||
Short: "Promote a node to a manager in the swarm",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runPromote(dockerCli, flags, args)
|
||||
return runPromote(dockerCli, args)
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPromote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string) error {
|
||||
for _, id := range args {
|
||||
if err := runUpdate(dockerCli, id, func(node *swarm.Node) {
|
||||
node.Spec.Role = swarm.NodeRoleManager
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to promote a node to a manager in the swarm.")
|
||||
func runPromote(dockerCli *client.DockerCli, nodes []string) error {
|
||||
promote := func(node *swarm.Node) {
|
||||
node.Spec.Role = swarm.NodeRoleManager
|
||||
}
|
||||
|
||||
return nil
|
||||
success := func(nodeID string) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", nodeID)
|
||||
}
|
||||
return updateNodes(dockerCli, nodes, promote, success)
|
||||
}
|
||||
|
||||
@@ -44,21 +44,20 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
nodeRef, err := nodeReference(client, ctx, opts.nodeID)
|
||||
nodeRef, err := Reference(client, ctx, opts.nodeID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
node, err := client.NodeInspect(ctx, nodeRef)
|
||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := opts.filter.Value()
|
||||
filter.Add("node", node.ID)
|
||||
if !opts.all {
|
||||
filter.Add("desired_state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired_state", string(swarm.TaskStateAccepted))
|
||||
|
||||
if !opts.all && !filter.Include("desired-state") {
|
||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -14,87 +13,71 @@ import (
|
||||
|
||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var opts nodeOptions
|
||||
var flags *pflag.FlagSet
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update [OPTIONS] NODE",
|
||||
Short: "Update a node",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags))
|
||||
return runUpdate(dockerCli, cmd.Flags(), args[0])
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
flags.StringVar(&opts.role, "role", "", "Role of the node (worker/manager)")
|
||||
flags.StringVar(&opts.membership, "membership", "", "Membership of the node (accepted/rejected)")
|
||||
flags.StringVar(&opts.availability, "availability", "", "Availability of the node (active/pause/drain)")
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.role, flagRole, "", "Role of the node (worker/manager)")
|
||||
flags.StringVar(&opts.membership, flagMembership, "", "Membership of the node (accepted/rejected)")
|
||||
flags.StringVar(&opts.availability, flagAvailability, "", "Availability of the node (active/pause/drain)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node *swarm.Node)) error {
|
||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, nodeID string) error {
|
||||
success := func(_ string) {
|
||||
fmt.Fprintln(dockerCli.Out(), nodeID)
|
||||
}
|
||||
return updateNodes(dockerCli, []string{nodeID}, mergeNodeUpdate(flags), success)
|
||||
}
|
||||
|
||||
func updateNodes(dockerCli *client.DockerCli, nodes []string, mergeNode func(node *swarm.Node), success func(nodeID string)) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
node, err := client.NodeInspect(ctx, nodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, nodeID := range nodes {
|
||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mergeNode(&node)
|
||||
err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
mergeNode(&node)
|
||||
err = client.NodeUpdate(ctx, node.ID, node.Version, node.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
success(nodeID)
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
|
||||
return func(node *swarm.Node) {
|
||||
mergeString := func(flag string, field *string) {
|
||||
if flags.Changed(flag) {
|
||||
*field, _ = flags.GetString(flag)
|
||||
}
|
||||
}
|
||||
|
||||
mergeRole := func(flag string, field *swarm.NodeRole) {
|
||||
if flags.Changed(flag) {
|
||||
str, _ := flags.GetString(flag)
|
||||
*field = swarm.NodeRole(str)
|
||||
}
|
||||
}
|
||||
|
||||
mergeMembership := func(flag string, field *swarm.NodeMembership) {
|
||||
if flags.Changed(flag) {
|
||||
str, _ := flags.GetString(flag)
|
||||
*field = swarm.NodeMembership(str)
|
||||
}
|
||||
}
|
||||
|
||||
mergeAvailability := func(flag string, field *swarm.NodeAvailability) {
|
||||
if flags.Changed(flag) {
|
||||
str, _ := flags.GetString(flag)
|
||||
*field = swarm.NodeAvailability(str)
|
||||
}
|
||||
}
|
||||
|
||||
mergeLabels := func(flag string, field *map[string]string) {
|
||||
if flags.Changed(flag) {
|
||||
values, _ := flags.GetStringSlice(flag)
|
||||
for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
|
||||
(*field)[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spec := &node.Spec
|
||||
mergeString("name", &spec.Name)
|
||||
// TODO: setting labels is not working
|
||||
mergeLabels("label", &spec.Labels)
|
||||
mergeRole("role", &spec.Role)
|
||||
mergeMembership("membership", &spec.Membership)
|
||||
mergeAvailability("availability", &spec.Availability)
|
||||
|
||||
if flags.Changed(flagRole) {
|
||||
str, _ := flags.GetString(flagRole)
|
||||
spec.Role = swarm.NodeRole(str)
|
||||
}
|
||||
if flags.Changed(flagMembership) {
|
||||
str, _ := flags.GetString(flagMembership)
|
||||
spec.Membership = swarm.NodeMembership(str)
|
||||
}
|
||||
if flags.Changed(flagAvailability) {
|
||||
str, _ := flags.GetString(flagAvailability)
|
||||
spec.Availability = swarm.NodeAvailability(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
flagRole = "role"
|
||||
flagMembership = "membership"
|
||||
flagAvailability = "availability"
|
||||
)
|
||||
|
||||
@@ -3,21 +3,39 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "disable",
|
||||
Use: "disable PLUGIN",
|
||||
Short: "Disable a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dockerCli.Client().PluginDisable(context.Background(), args[0])
|
||||
return runDisable(dockerCli, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDisable(dockerCli *client.DockerCli, name string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginDisable(context.Background(), ref.String())
|
||||
}
|
||||
|
||||
@@ -3,21 +3,39 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "enable",
|
||||
Use: "enable PLUGIN",
|
||||
Short: "Enable a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dockerCli.Client().PluginEnable(context.Background(), args[0])
|
||||
return runEnable(dockerCli, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEnable(dockerCli *client.DockerCli, name string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginEnable(context.Background(), ref.String())
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ package plugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect",
|
||||
Use: "inspect PLUGIN",
|
||||
Short: "Inspect a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -25,7 +27,18 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runInspect(dockerCli *client.DockerCli, name string) error {
|
||||
p, err := dockerCli.Client().PluginInspect(context.Background(), name)
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
p, err := dockerCli.Client().PluginInspect(context.Background(), ref.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,35 +3,52 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type pluginOptions struct {
|
||||
name string
|
||||
grantPerms bool
|
||||
disable bool
|
||||
}
|
||||
|
||||
func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var options pluginOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "install",
|
||||
Use: "install PLUGIN",
|
||||
Short: "Install a plugin",
|
||||
Args: cli.RequiresMinArgs(1), // TODO: allow for set args
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInstall(dockerCli, args[0], args[1:])
|
||||
options.name = args[0]
|
||||
return runInstall(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "grant all permissions necessary to run the plugin")
|
||||
flags.BoolVar(&options.disable, "disable", false, "do not enable the plugin on install")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error {
|
||||
named, err := reference.ParseNamed(opts.name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
named = reference.WithDefaultTag(named)
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
@@ -40,12 +57,44 @@ func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(named)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
||||
|
||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: pass acceptAllPermissions and noEnable flag
|
||||
return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, false, false, dockerCli.In(), dockerCli.Out())
|
||||
|
||||
registryAuthFunc := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install")
|
||||
|
||||
options := types.PluginInstallOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Disabled: opts.disable,
|
||||
AcceptAllPermissions: opts.grantPerms,
|
||||
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
|
||||
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
|
||||
PrivilegeFunc: registryAuthFunc,
|
||||
}
|
||||
|
||||
return dockerCli.Client().PluginInstall(ctx, ref.String(), options)
|
||||
}
|
||||
|
||||
func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
||||
return func(privileges types.PluginPrivileges) (bool, error) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
|
||||
for _, privilege := range privileges {
|
||||
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
|
||||
}
|
||||
|
||||
fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ")
|
||||
reader := bufio.NewReader(dockerCli.In())
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.ToLower(string(line)) == "y", nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
func newPushCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push",
|
||||
Use: "push PLUGIN",
|
||||
Short: "Push a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -31,7 +31,9 @@ func runPush(dockerCli *client.DockerCli, name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
named = reference.WithDefaultTag(named)
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
@@ -40,6 +42,9 @@ func runPush(dockerCli *client.DockerCli, name string) error {
|
||||
ctx := context.Background()
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(named)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authConfig := dockerCli.ResolveAuthConfig(ctx, repoInfo.Index)
|
||||
|
||||
encodedAuth, err := client.EncodeAuthToBase64(authConfig)
|
||||
|
||||
@@ -7,13 +7,14 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm",
|
||||
Use: "rm PLUGIN",
|
||||
Short: "Remove a plugin",
|
||||
Aliases: []string{"remove"},
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
@@ -28,8 +29,19 @@ func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
func runRemove(dockerCli *client.DockerCli, names []string) error {
|
||||
var errs cli.Errors
|
||||
for _, name := range names {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
// TODO: pass names to api instead of making multiple api calls
|
||||
if err := dockerCli.Client().PluginRemove(context.Background(), name); err != nil {
|
||||
if err := dockerCli.Client().PluginRemove(context.Background(), ref.String()); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -3,16 +3,19 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set",
|
||||
Use: "set PLUGIN key1=value1 [key2=value2...]",
|
||||
Short: "Change settings for a plugin",
|
||||
Args: cli.RequiresMinArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -24,5 +27,16 @@ func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runSet(dockerCli *client.DockerCli, name string, args []string) error {
|
||||
return dockerCli.Client().PluginSet(context.Background(), name, args)
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginSet(context.Background(), ref.String(), args)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
registrytypes "github.com/docker/engine-api/types/registry"
|
||||
@@ -42,7 +43,7 @@ func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
|
||||
// RegistryAuthenticationPrivilegedFunc return a RequestPrivilegeFunc from the specified registry index info
|
||||
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
|
||||
// for the given command.
|
||||
func (cli *DockerCli) RegistryAuthenticationPrivilegedFunc(index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
|
||||
return func() (string, error) {
|
||||
@@ -103,14 +104,14 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
|
||||
// will hit this if you attempt docker login from mintty where stdin
|
||||
// is a pipe, not a character based console.
|
||||
if flPassword == "" && !cli.isTerminalIn {
|
||||
return authconfig, fmt.Errorf("Error: Cannot perform an interactive logon from a non TTY device")
|
||||
return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
|
||||
}
|
||||
|
||||
authconfig.Username = strings.TrimSpace(authconfig.Username)
|
||||
|
||||
if flUser = strings.TrimSpace(flUser); flUser == "" {
|
||||
if isDefaultRegistry {
|
||||
// if this is a defauly registry (docker hub), then display the following message.
|
||||
// if this is a default registry (docker hub), then display the following message.
|
||||
fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
|
||||
}
|
||||
cli.promptWithDefault("Username", authconfig.Username)
|
||||
@@ -148,6 +149,34 @@ func (cli *DockerCli) ConfigureAuth(flUser, flPassword, serverAddress string, is
|
||||
return authconfig, nil
|
||||
}
|
||||
|
||||
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string
|
||||
func (cli *DockerCli) resolveAuthConfigFromImage(ctx context.Context, image string) (types.AuthConfig, error) {
|
||||
registryRef, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
}
|
||||
repoInfo, err := registry.ParseRepositoryInfo(registryRef)
|
||||
if err != nil {
|
||||
return types.AuthConfig{}, err
|
||||
}
|
||||
authConfig := cli.ResolveAuthConfig(ctx, repoInfo.Index)
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
|
||||
func (cli *DockerCli) RetrieveAuthTokenFromImage(ctx context.Context, image string) (string, error) {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
authConfig, err := cli.resolveAuthConfigFromImage(ctx, image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
encodedAuth, err := EncodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return encodedAuth, nil
|
||||
}
|
||||
|
||||
func readInput(in io.Reader, out io.Writer) string {
|
||||
reader := bufio.NewReader(in)
|
||||
line, _, err := reader.ReadLine()
|
||||
|
||||
@@ -45,7 +45,7 @@ func runLogout(dockerCli *client.DockerCli, serverAddress string) error {
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "Remove login credentials for %s\n", serverAddress)
|
||||
if err := client.EraseCredentials(dockerCli.ConfigFile(), serverAddress); err != nil {
|
||||
fmt.Fprintf(dockerCli.Out(), "WARNING: could not erase credentials: %v\n", err)
|
||||
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
@@ -24,20 +25,35 @@ func newCreateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return runCreate(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
|
||||
addServiceFlags(cmd, opts)
|
||||
cmd.Flags().SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(dockerCli *client.DockerCli, opts *serviceOptions) error {
|
||||
client := dockerCli.Client()
|
||||
apiClient := dockerCli.Client()
|
||||
createOpts := types.ServiceCreateOptions{}
|
||||
|
||||
service, err := opts.ToService()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := client.ServiceCreate(context.Background(), service)
|
||||
ctx := context.Background()
|
||||
|
||||
// only send auth if flag was set
|
||||
if opts.registryAuth {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, opts.image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createOpts.EncodedRegistryAuth = encodedAuth
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceCreate(ctx, service, createOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
apiclient "github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] SERVICE [SERVICE...]",
|
||||
Short: "Inspect a service",
|
||||
Short: "Display detailed information on one or more services",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.refs = args
|
||||
@@ -50,7 +51,7 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
service, err := client.ServiceInspect(ctx, ref)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref)
|
||||
if err == nil || !apiclient.IsErrServiceNotFound(err) {
|
||||
return service, nil, err
|
||||
}
|
||||
@@ -93,22 +94,61 @@ func printService(out io.Writer, service swarm.Service) {
|
||||
}
|
||||
|
||||
if service.Spec.Mode.Global != nil {
|
||||
fmt.Fprintln(out, "Mode:\t\tGLOBAL")
|
||||
fmt.Fprintln(out, "Mode:\t\tGlobal")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Mode:\t\tREPLICATED")
|
||||
fmt.Fprintln(out, "Mode:\t\tReplicated")
|
||||
if service.Spec.Mode.Replicated.Replicas != nil {
|
||||
fmt.Fprintf(out, " Replicas:\t\t%d\n", *service.Spec.Mode.Replicated.Replicas)
|
||||
fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(out, "Placement:")
|
||||
fmt.Fprintln(out, " Strategy:\tSPREAD")
|
||||
fmt.Fprintf(out, "UpateConfig:\n")
|
||||
fmt.Fprintln(out, " Strategy:\tSpread")
|
||||
if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
|
||||
ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
|
||||
}
|
||||
fmt.Fprintf(out, "UpdateConfig:\n")
|
||||
fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
|
||||
if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
|
||||
fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
|
||||
}
|
||||
fmt.Fprintf(out, "ContainerSpec:\n")
|
||||
printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
|
||||
|
||||
if service.Spec.TaskTemplate.Resources != nil {
|
||||
fmt.Fprintln(out, "Resources:")
|
||||
printResources := func(out io.Writer, r *swarm.Resources) {
|
||||
if r.NanoCPUs != 0 {
|
||||
fmt.Fprintf(out, " CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
|
||||
}
|
||||
if r.MemoryBytes != 0 {
|
||||
fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
|
||||
}
|
||||
}
|
||||
if service.Spec.TaskTemplate.Resources.Reservations != nil {
|
||||
fmt.Fprintln(out, "Reservations:")
|
||||
printResources(out, service.Spec.TaskTemplate.Resources.Reservations)
|
||||
}
|
||||
if service.Spec.TaskTemplate.Resources.Limits != nil {
|
||||
fmt.Fprintln(out, "Limits:")
|
||||
printResources(out, service.Spec.TaskTemplate.Resources.Limits)
|
||||
}
|
||||
}
|
||||
if len(service.Spec.Networks) > 0 {
|
||||
fmt.Fprintf(out, "Networks:")
|
||||
for _, n := range service.Spec.Networks {
|
||||
fmt.Fprintf(out, " %s", n.Target)
|
||||
}
|
||||
}
|
||||
|
||||
if len(service.Endpoint.Ports) > 0 {
|
||||
fmt.Fprintln(out, "Ports:")
|
||||
for _, port := range service.Endpoint.Ports {
|
||||
fmt.Fprintf(out, " Name = %s\n", port.Name)
|
||||
fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
|
||||
fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
|
||||
fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
|
||||
@@ -117,11 +157,20 @@ func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
|
||||
fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " "))
|
||||
}
|
||||
if len(containerSpec.Args) > 0 {
|
||||
fmt.Fprintf(out, " Args:\t%s\n", strings.Join(containerSpec.Args, " "))
|
||||
fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
|
||||
}
|
||||
if len(containerSpec.Env) > 0 {
|
||||
fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
|
||||
}
|
||||
ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
|
||||
ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
|
||||
if len(containerSpec.Mounts) > 0 {
|
||||
fmt.Fprintln(out, " Mounts:")
|
||||
for _, v := range containerSpec.Mounts {
|
||||
fmt.Fprintf(out, " Target = %s\n", v.Target)
|
||||
fmt.Fprintf(out, " Source = %s\n", v.Source)
|
||||
fmt.Fprintf(out, " ReadOnly = %v\n", v.ReadOnly)
|
||||
fmt.Fprintf(out, " Type = %v\n", v.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -47,11 +47,10 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
|
||||
services, err := client.ServiceList(
|
||||
context.Background(),
|
||||
types.ServiceListOptions{Filter: opts.filter.Value()})
|
||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: opts.filter.Value()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,31 +59,59 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
||||
if opts.quiet {
|
||||
printQuiet(out, services)
|
||||
} else {
|
||||
printTable(out, services)
|
||||
taskFilter := filters.NewArgs()
|
||||
for _, service := range services {
|
||||
taskFilter.Add("service", service.ID)
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
activeNodes := make(map[string]struct{})
|
||||
for _, n := range nodes {
|
||||
if n.Status.State == swarm.NodeStateReady {
|
||||
activeNodes[n.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
running := map[string]int{}
|
||||
for _, task := range tasks {
|
||||
if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == "running" {
|
||||
running[task.ServiceID]++
|
||||
}
|
||||
}
|
||||
|
||||
printTable(out, services, running)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTable(out io.Writer, services []swarm.Service) {
|
||||
func printTable(out io.Writer, services []swarm.Service, running map[string]int) {
|
||||
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
|
||||
|
||||
// Ignore flushing errors
|
||||
defer writer.Flush()
|
||||
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "SCALE", "IMAGE", "COMMAND")
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "REPLICAS", "IMAGE", "COMMAND")
|
||||
for _, service := range services {
|
||||
scale := ""
|
||||
replicas := ""
|
||||
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
|
||||
scale = fmt.Sprintf("%d", *service.Spec.Mode.Replicated.Replicas)
|
||||
replicas = fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas)
|
||||
} else if service.Spec.Mode.Global != nil {
|
||||
scale = "global"
|
||||
replicas = "global"
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
listItemFmt,
|
||||
stringid.TruncateID(service.ID),
|
||||
service.Spec.Name,
|
||||
scale,
|
||||
replicas,
|
||||
service.Spec.TaskTemplate.ContainerSpec.Image,
|
||||
strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "))
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type int64Value interface {
|
||||
type memBytes int64
|
||||
|
||||
func (m *memBytes) String() string {
|
||||
return strconv.FormatInt(m.Value(), 10)
|
||||
return units.BytesSize(float64(m.Value()))
|
||||
}
|
||||
|
||||
func (m *memBytes) Set(value string) error {
|
||||
@@ -48,7 +48,7 @@ func (m *memBytes) Value() int64 {
|
||||
type nanoCPUs int64
|
||||
|
||||
func (c *nanoCPUs) String() string {
|
||||
return strconv.FormatInt(c.Value(), 10)
|
||||
return big.NewRat(c.Value(), 1e9).FloatString(3)
|
||||
}
|
||||
|
||||
func (c *nanoCPUs) Set(value string) error {
|
||||
@@ -160,6 +160,13 @@ func (m *MountOpt) Set(value string) error {
|
||||
return mount.VolumeOptions
|
||||
}
|
||||
|
||||
bindOptions := func() *swarm.BindOptions {
|
||||
if mount.BindOptions == nil {
|
||||
mount.BindOptions = new(swarm.BindOptions)
|
||||
}
|
||||
return mount.BindOptions
|
||||
}
|
||||
|
||||
setValueOnMap := func(target map[string]string, value string) {
|
||||
parts := strings.SplitN(value, "=", 2)
|
||||
if len(parts) == 1 {
|
||||
@@ -169,15 +176,21 @@ func (m *MountOpt) Set(value string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Set writable as the default
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" {
|
||||
mount.Writable = true
|
||||
if len(parts) == 1 && strings.ToLower(parts[0]) == "readonly" {
|
||||
mount.ReadOnly = true
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 1 && strings.ToLower(parts[0]) == "volume-nocopy" {
|
||||
volumeOptions().NoCopy = true
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invald field '%s' must be a key=value pair", field)
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
key, value := parts[0], parts[1]
|
||||
@@ -188,17 +201,18 @@ func (m *MountOpt) Set(value string) error {
|
||||
mount.Source = value
|
||||
case "target":
|
||||
mount.Target = value
|
||||
case "writable":
|
||||
mount.Writable, err = strconv.ParseBool(value)
|
||||
case "readonly":
|
||||
ro, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invald value for writable: %s", err.Error())
|
||||
return fmt.Errorf("invalid value for readonly: %s", value)
|
||||
}
|
||||
mount.ReadOnly = ro
|
||||
case "bind-propagation":
|
||||
mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
||||
case "volume-populate":
|
||||
volumeOptions().Populate, err = strconv.ParseBool(value)
|
||||
bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
||||
case "volume-nocopy":
|
||||
volumeOptions().NoCopy, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invald value for populate: %s", err.Error())
|
||||
return fmt.Errorf("invalid value for populate: %s", value)
|
||||
}
|
||||
case "volume-label":
|
||||
setValueOnMap(volumeOptions().Labels, value)
|
||||
@@ -210,7 +224,7 @@ func (m *MountOpt) Set(value string) error {
|
||||
}
|
||||
setValueOnMap(volumeOptions().DriverConfig.Options, value)
|
||||
default:
|
||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, value)
|
||||
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +236,17 @@ func (m *MountOpt) Set(value string) error {
|
||||
return fmt.Errorf("target is required")
|
||||
}
|
||||
|
||||
if mount.VolumeOptions != nil && mount.Source == "" {
|
||||
return fmt.Errorf("source is required when specifying volume-* options")
|
||||
}
|
||||
|
||||
if mount.Type == swarm.MountType("BIND") && mount.VolumeOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", swarm.MountTypeBind)
|
||||
}
|
||||
if mount.Type == swarm.MountType("VOLUME") && mount.BindOptions != nil {
|
||||
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", swarm.MountTypeVolume)
|
||||
}
|
||||
|
||||
m.values = append(m.values, mount)
|
||||
return nil
|
||||
}
|
||||
@@ -235,7 +260,8 @@ func (m *MountOpt) Type() string {
|
||||
func (m *MountOpt) String() string {
|
||||
mounts := []string{}
|
||||
for _, mount := range m.values {
|
||||
mounts = append(mounts, fmt.Sprintf("%v", mount))
|
||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
||||
mounts = append(mounts, repr)
|
||||
}
|
||||
return strings.Join(mounts, ", ")
|
||||
}
|
||||
@@ -309,7 +335,7 @@ func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
|
||||
}
|
||||
|
||||
return &swarm.EndpointSpec{
|
||||
Mode: swarm.ResolutionMode(e.mode),
|
||||
Mode: swarm.ResolutionMode(strings.ToLower(e.mode)),
|
||||
Ports: portConfigs,
|
||||
}
|
||||
}
|
||||
@@ -365,6 +391,8 @@ type serviceOptions struct {
|
||||
update updateOptions
|
||||
networks []string
|
||||
endpoint endpointOptions
|
||||
|
||||
registryAuth bool
|
||||
}
|
||||
|
||||
func newServiceOptions() *serviceOptions {
|
||||
@@ -428,7 +456,7 @@ func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
|
||||
return service, nil
|
||||
}
|
||||
|
||||
// addServiceFlags adds all flags that are common to both `create` and `update.
|
||||
// addServiceFlags adds all flags that are common to both `create` and `update`.
|
||||
// Any flags that are not common are added separately in the individual command
|
||||
func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
||||
flags := cmd.Flags()
|
||||
@@ -437,51 +465,55 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
||||
|
||||
flags.VarP(&opts.env, "env", "e", "Set environment variables")
|
||||
flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.StringVarP(&opts.user, "user", "u", "", "Username or UID")
|
||||
flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
|
||||
flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service")
|
||||
|
||||
flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
|
||||
flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
|
||||
flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
|
||||
flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
|
||||
flags.Var(&opts.stopGrace, "stop-grace-period", "Time to wait before force killing a container")
|
||||
flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container")
|
||||
|
||||
flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
|
||||
flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
|
||||
|
||||
flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on_failure, or any)")
|
||||
flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on-failure, or any)")
|
||||
flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts")
|
||||
flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
|
||||
flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evalulate the restart policy")
|
||||
flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
|
||||
|
||||
flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
|
||||
|
||||
flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously")
|
||||
flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
|
||||
flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
|
||||
|
||||
flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
|
||||
flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode(Valid values: VIP, DNSRR)")
|
||||
flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode (vip or dnsrr)")
|
||||
flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
|
||||
|
||||
flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to Swarm agents")
|
||||
}
|
||||
|
||||
const (
|
||||
flagConstraint = "constraint"
|
||||
flagName = "name"
|
||||
flagEndpointMode = "endpoint-mode"
|
||||
flagLabel = "label"
|
||||
flagLimitCPU = "limit-cpu"
|
||||
flagLimitMemory = "limit-memory"
|
||||
flagMode = "mode"
|
||||
flagMount = "mount"
|
||||
flagName = "name"
|
||||
flagNetwork = "network"
|
||||
flagPublish = "publish"
|
||||
flagReplicas = "replicas"
|
||||
flagReserveCPU = "reserve-cpu"
|
||||
flagReserveMemory = "reserve-memory"
|
||||
flagMount = "mount"
|
||||
flagMode = "mode"
|
||||
flagReplicas = "replicas"
|
||||
flagPublish = "publish"
|
||||
flagNetwork = "network"
|
||||
flagRestartCondition = "restart-condition"
|
||||
flagRestartDelay = "restart-delay"
|
||||
flagRestartMaxAttempts = "restart-max-attempts"
|
||||
flagRestartWindow = "restart-window"
|
||||
flagEndpointMode = "endpoint-mode"
|
||||
flagUpdateParallelism = "update-parallelism"
|
||||
flagStopGracePeriod = "stop-grace-period"
|
||||
flagUpdateDelay = "update-delay"
|
||||
flagUpdateParallelism = "update-parallelism"
|
||||
flagUser = "user"
|
||||
flagRegistryAuth = "registry-auth"
|
||||
)
|
||||
|
||||
163
api/client/service/opts_test.go
Normal file
163
api/client/service/opts_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
)
|
||||
|
||||
func TestMemBytesString(t *testing.T) {
|
||||
var mem memBytes = 1048576
|
||||
assert.Equal(t, mem.String(), "1 MiB")
|
||||
}
|
||||
|
||||
func TestMemBytesSetAndValue(t *testing.T) {
|
||||
var mem memBytes
|
||||
assert.NilError(t, mem.Set("5kb"))
|
||||
assert.Equal(t, mem.Value(), int64(5120))
|
||||
}
|
||||
|
||||
func TestNanoCPUsString(t *testing.T) {
|
||||
var cpus nanoCPUs = 6100000000
|
||||
assert.Equal(t, cpus.String(), "6.100")
|
||||
}
|
||||
|
||||
func TestNanoCPUsSetAndValue(t *testing.T) {
|
||||
var cpus nanoCPUs
|
||||
assert.NilError(t, cpus.Set("0.35"))
|
||||
assert.Equal(t, cpus.Value(), int64(350000000))
|
||||
}
|
||||
|
||||
func TestDurationOptString(t *testing.T) {
|
||||
dur := time.Duration(300 * 10e8)
|
||||
duration := DurationOpt{value: &dur}
|
||||
assert.Equal(t, duration.String(), "5m0s")
|
||||
}
|
||||
|
||||
func TestDurationOptSetAndValue(t *testing.T) {
|
||||
var duration DurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Equal(t, *duration.Value(), time.Duration(300*10e8))
|
||||
}
|
||||
|
||||
func TestUint64OptString(t *testing.T) {
|
||||
value := uint64(2345678)
|
||||
opt := Uint64Opt{value: &value}
|
||||
assert.Equal(t, opt.String(), "2345678")
|
||||
|
||||
opt = Uint64Opt{}
|
||||
assert.Equal(t, opt.String(), "none")
|
||||
}
|
||||
|
||||
func TestUint64OptSetAndValue(t *testing.T) {
|
||||
var opt Uint64Opt
|
||||
assert.NilError(t, opt.Set("14445"))
|
||||
assert.Equal(t, *opt.Value(), uint64(14445))
|
||||
}
|
||||
|
||||
func TestMountOptString(t *testing.T) {
|
||||
mount := MountOpt{
|
||||
values: []swarm.Mount{
|
||||
{
|
||||
Type: swarm.MountType("BIND"),
|
||||
Source: "/home/path",
|
||||
Target: "/target",
|
||||
},
|
||||
{
|
||||
Type: swarm.MountType("VOLUME"),
|
||||
Source: "foo",
|
||||
Target: "/target/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
expected := "BIND /home/path /target, VOLUME foo /target/foo"
|
||||
assert.Equal(t, mount.String(), expected)
|
||||
}
|
||||
|
||||
func TestMountOptSetNoError(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo"))
|
||||
|
||||
mounts := mount.Value()
|
||||
assert.Equal(t, len(mounts), 1)
|
||||
assert.Equal(t, mounts[0], swarm.Mount{
|
||||
Type: swarm.MountType("BIND"),
|
||||
Source: "/foo",
|
||||
Target: "/target",
|
||||
})
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorNoType(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorNoTarget(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,source=/foo"), "target is required")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidKey(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidField(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidWritable(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,readonly=no"), "invalid value for readonly: no")
|
||||
}
|
||||
|
||||
func TestMountOptDefaultEnableWritable(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo"))
|
||||
assert.Equal(t, m.values[0].ReadOnly, false)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly"))
|
||||
assert.Equal(t, m.values[0].ReadOnly, true)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1"))
|
||||
assert.Equal(t, m.values[0].ReadOnly, true)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0"))
|
||||
assert.Equal(t, m.values[0].ReadOnly, false)
|
||||
}
|
||||
|
||||
func TestMountOptVolumeNoCopy(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.Error(t, m.Set("type=volume,target=/foo,volume-nocopy"), "source is required")
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo"))
|
||||
assert.Equal(t, m.values[0].VolumeOptions == nil, true)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true"))
|
||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy"))
|
||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
||||
|
||||
m = MountOpt{}
|
||||
assert.NilError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1"))
|
||||
assert.Equal(t, m.values[0].VolumeOptions != nil, true)
|
||||
assert.Equal(t, m.values[0].VolumeOptions.NoCopy, true)
|
||||
}
|
||||
|
||||
func TestMountOptTypeConflict(t *testing.T) {
|
||||
var m MountOpt
|
||||
assert.Error(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
|
||||
assert.Error(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix")
|
||||
}
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "scale SERVICE=SCALE [SERVICE=SCALE...]",
|
||||
Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
|
||||
Short: "Scale one or multiple services",
|
||||
Args: scaleArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -61,7 +62,8 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := client.ServiceInspect(ctx, serviceID)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,7 +78,7 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string
|
||||
}
|
||||
serviceMode.Replicated.Replicas = &uintScale
|
||||
|
||||
err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
|
||||
err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/api/client/idresolver"
|
||||
"github.com/docker/docker/api/client/node"
|
||||
"github.com/docker/docker/api/client/task"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
@@ -44,16 +45,28 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := client.ServiceInspect(ctx, opts.serviceID)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, opts.serviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filter := opts.filter.Value()
|
||||
filter.Add("service", service.ID)
|
||||
if !opts.all && !filter.Include("desired_state") {
|
||||
filter.Add("desired_state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired_state", string(swarm.TaskStateAccepted))
|
||||
if !opts.all && !filter.Include("desired-state") {
|
||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
||||
}
|
||||
|
||||
if filter.Include("node") {
|
||||
nodeFilters := filter.Get("node")
|
||||
for _, nodeFilter := range nodeFilters {
|
||||
nodeReference, err := node.Reference(client, ctx, nodeFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter.Del("node", nodeFilter)
|
||||
filter.Add("node", nodeReference)
|
||||
}
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -18,18 +19,17 @@ import (
|
||||
|
||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := newServiceOptions()
|
||||
var flags *pflag.FlagSet
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update [OPTIONS] SERVICE",
|
||||
Short: "Update a service",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUpdate(dockerCli, flags, args[0])
|
||||
return runUpdate(dockerCli, cmd.Flags(), args[0])
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
flags := cmd.Flags()
|
||||
flags.String("image", "", "Service image tag")
|
||||
flags.StringSlice("command", []string{}, "Service command")
|
||||
flags.StringSlice("arg", []string{}, "Service command args")
|
||||
@@ -38,19 +38,37 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID string) error {
|
||||
client := dockerCli.Client()
|
||||
apiClient := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
updateOpts := types.ServiceUpdateOptions{}
|
||||
|
||||
service, err := client.ServiceInspect(ctx, serviceID)
|
||||
service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = mergeService(&service.Spec, flags)
|
||||
err = updateService(flags, &service.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = client.ServiceUpdate(ctx, service.ID, service.Version, service.Spec)
|
||||
|
||||
// only send auth if flag was set
|
||||
sendAuth, err := flags.GetBool(flagRegistryAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sendAuth {
|
||||
// Retrieve encoded auth token from the image reference
|
||||
// This would be the old image if it didn't change in this update
|
||||
image := service.Spec.TaskTemplate.ContainerSpec.Image
|
||||
encodedAuth, err := dockerCli.RetrieveAuthTokenFromImage(ctx, image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updateOpts.EncodedRegistryAuth = encodedAuth
|
||||
}
|
||||
|
||||
err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, updateOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,52 +77,52 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID stri
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
|
||||
func updateService(flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
|
||||
|
||||
mergeString := func(flag string, field *string) {
|
||||
updateString := func(flag string, field *string) {
|
||||
if flags.Changed(flag) {
|
||||
*field, _ = flags.GetString(flag)
|
||||
}
|
||||
}
|
||||
|
||||
mergeListOpts := func(flag string, field *[]string) {
|
||||
updateListOpts := func(flag string, field *[]string) {
|
||||
if flags.Changed(flag) {
|
||||
value := flags.Lookup(flag).Value.(*opts.ListOpts)
|
||||
*field = value.GetAll()
|
||||
}
|
||||
}
|
||||
|
||||
mergeSlice := func(flag string, field *[]string) {
|
||||
updateSlice := func(flag string, field *[]string) {
|
||||
if flags.Changed(flag) {
|
||||
*field, _ = flags.GetStringSlice(flag)
|
||||
}
|
||||
}
|
||||
|
||||
mergeInt64Value := func(flag string, field *int64) {
|
||||
updateInt64Value := func(flag string, field *int64) {
|
||||
if flags.Changed(flag) {
|
||||
*field = flags.Lookup(flag).Value.(int64Value).Value()
|
||||
}
|
||||
}
|
||||
|
||||
mergeDuration := func(flag string, field *time.Duration) {
|
||||
updateDuration := func(flag string, field *time.Duration) {
|
||||
if flags.Changed(flag) {
|
||||
*field, _ = flags.GetDuration(flag)
|
||||
}
|
||||
}
|
||||
|
||||
mergeDurationOpt := func(flag string, field *time.Duration) {
|
||||
updateDurationOpt := func(flag string, field *time.Duration) {
|
||||
if flags.Changed(flag) {
|
||||
*field = *flags.Lookup(flag).Value.(*DurationOpt).Value()
|
||||
}
|
||||
}
|
||||
|
||||
mergeUint64 := func(flag string, field *uint64) {
|
||||
updateUint64 := func(flag string, field *uint64) {
|
||||
if flags.Changed(flag) {
|
||||
*field, _ = flags.GetUint64(flag)
|
||||
}
|
||||
}
|
||||
|
||||
mergeUint64Opt := func(flag string, field *uint64) {
|
||||
updateUint64Opt := func(flag string, field *uint64) {
|
||||
if flags.Changed(flag) {
|
||||
*field = *flags.Lookup(flag).Value.(*Uint64Opt).Value()
|
||||
}
|
||||
@@ -112,37 +130,39 @@ func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
|
||||
|
||||
cspec := &spec.TaskTemplate.ContainerSpec
|
||||
task := &spec.TaskTemplate
|
||||
mergeString(flagName, &spec.Name)
|
||||
mergeLabels(flags, &spec.Labels)
|
||||
mergeString("image", &cspec.Image)
|
||||
mergeSlice("command", &cspec.Command)
|
||||
mergeSlice("arg", &cspec.Command)
|
||||
mergeListOpts("env", &cspec.Env)
|
||||
mergeString("workdir", &cspec.Dir)
|
||||
mergeString("user", &cspec.User)
|
||||
mergeMounts(flags, &cspec.Mounts)
|
||||
|
||||
if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
|
||||
taskResources := func() *swarm.ResourceRequirements {
|
||||
if task.Resources == nil {
|
||||
task.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
task.Resources.Limits = &swarm.Resources{}
|
||||
mergeInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
|
||||
mergeInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
|
||||
return task.Resources
|
||||
}
|
||||
|
||||
updateString(flagName, &spec.Name)
|
||||
updateLabels(flags, &spec.Labels)
|
||||
updateString("image", &cspec.Image)
|
||||
updateSlice("command", &cspec.Command)
|
||||
updateSlice("arg", &cspec.Args)
|
||||
updateListOpts("env", &cspec.Env)
|
||||
updateString("workdir", &cspec.Dir)
|
||||
updateString(flagUser, &cspec.User)
|
||||
updateMounts(flags, &cspec.Mounts)
|
||||
|
||||
if flags.Changed(flagLimitCPU) || flags.Changed(flagLimitMemory) {
|
||||
taskResources().Limits = &swarm.Resources{}
|
||||
updateInt64Value(flagLimitCPU, &task.Resources.Limits.NanoCPUs)
|
||||
updateInt64Value(flagLimitMemory, &task.Resources.Limits.MemoryBytes)
|
||||
|
||||
}
|
||||
if flags.Changed(flagReserveCPU) || flags.Changed(flagReserveMemory) {
|
||||
if task.Resources == nil {
|
||||
task.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
task.Resources.Reservations = &swarm.Resources{}
|
||||
mergeInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
|
||||
mergeInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
|
||||
taskResources().Reservations = &swarm.Resources{}
|
||||
updateInt64Value(flagReserveCPU, &task.Resources.Reservations.NanoCPUs)
|
||||
updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
|
||||
}
|
||||
|
||||
mergeDurationOpt("stop-grace-period", cspec.StopGracePeriod)
|
||||
updateDurationOpt(flagStopGracePeriod, cspec.StopGracePeriod)
|
||||
|
||||
if flags.Changed(flagRestartCondition) || flags.Changed(flagRestartDelay) || flags.Changed(flagRestartMaxAttempts) || flags.Changed(flagRestartWindow) {
|
||||
if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
|
||||
if task.RestartPolicy == nil {
|
||||
task.RestartPolicy = &swarm.RestartPolicy{}
|
||||
}
|
||||
@@ -151,29 +171,29 @@ func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
|
||||
value, _ := flags.GetString(flagRestartCondition)
|
||||
task.RestartPolicy.Condition = swarm.RestartPolicyCondition(value)
|
||||
}
|
||||
mergeDurationOpt(flagRestartDelay, task.RestartPolicy.Delay)
|
||||
mergeUint64Opt(flagRestartMaxAttempts, task.RestartPolicy.MaxAttempts)
|
||||
mergeDurationOpt((flagRestartWindow), task.RestartPolicy.Window)
|
||||
updateDurationOpt(flagRestartDelay, task.RestartPolicy.Delay)
|
||||
updateUint64Opt(flagRestartMaxAttempts, task.RestartPolicy.MaxAttempts)
|
||||
updateDurationOpt((flagRestartWindow), task.RestartPolicy.Window)
|
||||
}
|
||||
|
||||
if flags.Changed(flagConstraint) {
|
||||
task.Placement = &swarm.Placement{}
|
||||
mergeSlice(flagConstraint, &task.Placement.Constraints)
|
||||
updateSlice(flagConstraint, &task.Placement.Constraints)
|
||||
}
|
||||
|
||||
if err := mergeMode(flags, &spec.Mode); err != nil {
|
||||
if err := updateReplicas(flags, &spec.Mode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flags.Changed(flagUpdateParallelism) || flags.Changed(flagUpdateDelay) {
|
||||
if anyChanged(flags, flagUpdateParallelism, flagUpdateDelay) {
|
||||
if spec.UpdateConfig == nil {
|
||||
spec.UpdateConfig = &swarm.UpdateConfig{}
|
||||
}
|
||||
mergeUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
|
||||
mergeDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
|
||||
updateUint64(flagUpdateParallelism, &spec.UpdateConfig.Parallelism)
|
||||
updateDuration(flagUpdateDelay, &spec.UpdateConfig.Delay)
|
||||
}
|
||||
|
||||
mergeNetworks(flags, &spec.Networks)
|
||||
updateNetworks(flags, &spec.Networks)
|
||||
if flags.Changed(flagEndpointMode) {
|
||||
value, _ := flags.GetString(flagEndpointMode)
|
||||
spec.EndpointSpec.Mode = swarm.ResolutionMode(value)
|
||||
@@ -183,38 +203,45 @@ func mergeService(spec *swarm.ServiceSpec, flags *pflag.FlagSet) error {
|
||||
if spec.EndpointSpec == nil {
|
||||
spec.EndpointSpec = &swarm.EndpointSpec{}
|
||||
}
|
||||
mergePorts(flags, &spec.EndpointSpec.Ports)
|
||||
updatePorts(flags, &spec.EndpointSpec.Ports)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeLabels(flags *pflag.FlagSet, field *map[string]string) {
|
||||
func updateLabels(flags *pflag.FlagSet, field *map[string]string) {
|
||||
if !flags.Changed(flagLabel) {
|
||||
return
|
||||
}
|
||||
|
||||
if *field == nil {
|
||||
*field = make(map[string]string)
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagLabel).Value.(*opts.ListOpts).GetAll()
|
||||
|
||||
localLabels := map[string]string{}
|
||||
for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
|
||||
(*field)[key] = value
|
||||
localLabels[key] = value
|
||||
}
|
||||
*field = localLabels
|
||||
}
|
||||
|
||||
func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
|
||||
for _, flag := range fields {
|
||||
if flags.Changed(flag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: should this override by destination path, or does swarm handle that?
|
||||
func mergeMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
|
||||
func updateMounts(flags *pflag.FlagSet, mounts *[]swarm.Mount) {
|
||||
if !flags.Changed(flagMount) {
|
||||
return
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagMount).Value.(*MountOpt).Value()
|
||||
*mounts = append(*mounts, values...)
|
||||
*mounts = flags.Lookup(flagMount).Value.(*MountOpt).Value()
|
||||
}
|
||||
|
||||
// TODO: should this override by name, or does swarm handle that?
|
||||
func mergePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
|
||||
func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
|
||||
if !flags.Changed(flagPublish) {
|
||||
return
|
||||
}
|
||||
@@ -222,55 +249,34 @@ func mergePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) {
|
||||
values := flags.Lookup(flagPublish).Value.(*opts.ListOpts).GetAll()
|
||||
ports, portBindings, _ := nat.ParsePortSpecs(values)
|
||||
|
||||
var localPortConfig []swarm.PortConfig
|
||||
for port := range ports {
|
||||
*portConfig = append(*portConfig, convertPortToPortConfig(port, portBindings)...)
|
||||
localPortConfig = append(localPortConfig, convertPortToPortConfig(port, portBindings)...)
|
||||
}
|
||||
*portConfig = localPortConfig
|
||||
}
|
||||
|
||||
func mergeNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
|
||||
func updateNetworks(flags *pflag.FlagSet, attachments *[]swarm.NetworkAttachmentConfig) {
|
||||
if !flags.Changed(flagNetwork) {
|
||||
return
|
||||
}
|
||||
networks, _ := flags.GetStringSlice(flagNetwork)
|
||||
|
||||
var localAttachments []swarm.NetworkAttachmentConfig
|
||||
for _, network := range networks {
|
||||
*attachments = append(*attachments, swarm.NetworkAttachmentConfig{Target: network})
|
||||
localAttachments = append(localAttachments, swarm.NetworkAttachmentConfig{Target: network})
|
||||
}
|
||||
*attachments = localAttachments
|
||||
}
|
||||
|
||||
func mergeMode(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
|
||||
if !flags.Changed(flagMode) && !flags.Changed(flagReplicas) {
|
||||
func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error {
|
||||
if !flags.Changed(flagReplicas) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var mode string
|
||||
if flags.Changed(flagMode) {
|
||||
mode, _ = flags.GetString(flagMode)
|
||||
}
|
||||
|
||||
if !(mode == "replicated" || serviceMode.Replicated != nil) && flags.Changed(flagReplicas) {
|
||||
if serviceMode.Replicated == nil {
|
||||
return fmt.Errorf("replicas can only be used with replicated mode")
|
||||
}
|
||||
|
||||
if mode == "global" {
|
||||
serviceMode.Replicated = nil
|
||||
serviceMode.Global = &swarm.GlobalService{}
|
||||
return nil
|
||||
}
|
||||
|
||||
if flags.Changed(flagReplicas) {
|
||||
replicas := flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
|
||||
serviceMode.Replicated = &swarm.ReplicatedService{Replicas: replicas}
|
||||
serviceMode.Global = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
if mode == "replicated" {
|
||||
if serviceMode.Replicated != nil {
|
||||
return nil
|
||||
}
|
||||
serviceMode.Replicated = &swarm.ReplicatedService{Replicas: &DefaultReplicas}
|
||||
serviceMode.Global = nil
|
||||
}
|
||||
|
||||
serviceMode.Replicated.Replicas = flags.Lookup(flagReplicas).Value.(*Uint64Opt).Value()
|
||||
return nil
|
||||
}
|
||||
|
||||
26
api/client/service/update_test.go
Normal file
26
api/client/service/update_test.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
)
|
||||
|
||||
func TestUpdateServiceCommandAndArgs(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
flags.Set("command", "the")
|
||||
flags.Set("command", "new")
|
||||
flags.Set("command", "command")
|
||||
flags.Set("arg", "the")
|
||||
flags.Set("arg", "new args")
|
||||
|
||||
spec := &swarm.ServiceSpec{}
|
||||
cspec := &spec.TaskTemplate.ContainerSpec
|
||||
cspec.Command = []string{"old", "command"}
|
||||
cspec.Args = []string{"old", "args"}
|
||||
|
||||
updateService(flags, spec)
|
||||
assert.EqualStringSlice(t, cspec.Command, []string{"the", "new", "command"})
|
||||
assert.EqualStringSlice(t, cspec.Args, []string{"the", "new args"})
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand return a command for `docker deploy`
|
||||
// NewTopLevelDeployCommand returns a command for `docker deploy`
|
||||
func NewTopLevelDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := newDeployCommand(dockerCli)
|
||||
// Remove the aliases at the top level
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewStackCommand returns nocommand
|
||||
// NewStackCommand returns no command
|
||||
func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{}
|
||||
}
|
||||
|
||||
// NewTopLevelDeployCommand return no command
|
||||
// NewTopLevelDeployCommand returns no command
|
||||
func NewTopLevelDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ func newDeployCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy [OPTIONS] STACK",
|
||||
Aliases: []string{"up"},
|
||||
Short: "Create and update a stack",
|
||||
Short: "Create and update a stack from a Distributed Application Bundle (DAB)",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.namespace = args[0]
|
||||
@@ -184,18 +184,21 @@ func deployServices(
|
||||
if service, exists := existingServiceMap[name]; exists {
|
||||
fmt.Fprintf(out, "Updating service %s (id: %s)\n", name, service.ID)
|
||||
|
||||
// TODO(nishanttotla): Pass auth token
|
||||
if err := apiClient.ServiceUpdate(
|
||||
ctx,
|
||||
service.ID,
|
||||
service.Version,
|
||||
serviceSpec,
|
||||
types.ServiceUpdateOptions{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(out, "Creating service %s\n", name)
|
||||
|
||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec); err != nil {
|
||||
// TODO(nishanttotla): Pass headers with X-Registry-Auth
|
||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec, types.ServiceCreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
|
||||
flags.StringVarP(
|
||||
opt,
|
||||
"bundle", "f", "",
|
||||
"Path to a bundle (Default: STACK.dsb)")
|
||||
"Path to a Distributed Application Bundle file (Default: STACK.dab)")
|
||||
}
|
||||
|
||||
func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
|
||||
defaultPath := fmt.Sprintf("%s.dsb", namespace)
|
||||
defaultPath := fmt.Sprintf("%s.dab", namespace)
|
||||
|
||||
if path == "" {
|
||||
path = defaultPath
|
||||
@@ -31,7 +31,13 @@ func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefil
|
||||
}
|
||||
|
||||
fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
|
||||
bundle, err := bundlefile.LoadFile(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, fmt.Errorf("Error reading %s: %v\n", path, err)
|
||||
}
|
||||
|
||||
@@ -63,6 +63,11 @@ func runRemove(dockerCli *client.DockerCli, opts removeOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
if len(services) == 0 && len(networks) == 0 {
|
||||
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasError {
|
||||
return fmt.Errorf("Failed to remove some resources")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
@@ -43,14 +45,15 @@ func newTasksCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
||||
namespace := opts.namespace
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
filter := opts.filter.Value()
|
||||
filter.Add("label", labelNamespace+"="+opts.namespace)
|
||||
if !opts.all && !filter.Include("desired_state") {
|
||||
filter.Add("desired_state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired_state", string(swarm.TaskStateAccepted))
|
||||
if !opts.all && !filter.Include("desired-state") {
|
||||
filter.Add("desired-state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired-state", string(swarm.TaskStateAccepted))
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: filter})
|
||||
@@ -58,5 +61,10 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
fmt.Fprintf(dockerCli.Out(), "Nothing found in stack: %s\n", namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
return task.Print(dockerCli, ctx, tasks, idresolver.New(client, opts.noResolve))
|
||||
}
|
||||
|
||||
@@ -9,53 +9,87 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
generatedSecretEntropyBytes = 16
|
||||
generatedSecretBase = 36
|
||||
// floor(log(2^128-1, 36)) + 1
|
||||
maxGeneratedSecretLength = 25
|
||||
)
|
||||
|
||||
type initOptions struct {
|
||||
swarmOptions
|
||||
listenAddr NodeAddrOption
|
||||
autoAccept AutoAcceptOption
|
||||
forceNewCluster bool
|
||||
secret string
|
||||
}
|
||||
|
||||
func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := initOptions{
|
||||
listenAddr: NewNodeAddrOption(),
|
||||
autoAccept: NewAutoAcceptOption(),
|
||||
listenAddr: NewListenAddrOption(),
|
||||
swarmOptions: swarmOptions{
|
||||
autoAccept: NewAutoAcceptOption(),
|
||||
},
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize a Swarm.",
|
||||
Short: "Initialize a Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInit(dockerCli, opts)
|
||||
return runInit(dockerCli, cmd.Flags(), opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
|
||||
flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager, or none)")
|
||||
flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
|
||||
flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state.")
|
||||
addSwarmFlags(flags, &opts.swarmOptions)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInit(dockerCli *client.DockerCli, opts initOptions) error {
|
||||
func runInit(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts initOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
// If no secret was specified, we create a random one
|
||||
if !flags.Changed("secret") {
|
||||
opts.secret = generateRandomSecret()
|
||||
fmt.Fprintf(dockerCli.Out(), "No --secret provided. Generated random secret:\n\t%s\n\n", opts.secret)
|
||||
}
|
||||
|
||||
req := swarm.InitRequest{
|
||||
ListenAddr: opts.listenAddr.String(),
|
||||
ForceNewCluster: opts.forceNewCluster,
|
||||
Spec: opts.swarmOptions.ToSpec(),
|
||||
}
|
||||
|
||||
req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(opts.secret)
|
||||
|
||||
nodeID, err := client.SwarmInit(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Swarm initialized: current node (%s) is now a manager.\n", nodeID)
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "Swarm initialized: current node (%s) is now a manager.\n\n", nodeID)
|
||||
|
||||
// Fetch CAHash and Address from the API
|
||||
info, err := client.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
node, _, err := client.NodeInspectWithRaw(ctx, nodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if node.ManagerStatus != nil && info.Swarm.CACertHash != "" {
|
||||
var secretArgs string
|
||||
if opts.secret != "" {
|
||||
secretArgs = "--secret " + opts.secret
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\tdocker swarm join %s \\\n\t--ca-hash %s \\\n\t%s\n", secretArgs, info.Swarm.CACertHash, node.ManagerStatus.Addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,12 +20,12 @@ type joinOptions struct {
|
||||
|
||||
func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := joinOptions{
|
||||
listenAddr: NodeAddrOption{addr: defaultListenAddr},
|
||||
listenAddr: NewListenAddrOption(),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "join [OPTIONS] HOST:PORT",
|
||||
Short: "Join a Swarm as a node and/or manager.",
|
||||
Short: "Join a Swarm as a node and/or manager",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.remote = args[0]
|
||||
@@ -34,9 +34,9 @@ func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
|
||||
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address")
|
||||
flags.BoolVar(&opts.manager, "manager", false, "Try joining as a manager.")
|
||||
flags.StringVar(&opts.secret, "secret", "", "Secret for node acceptance")
|
||||
flags.StringVar(&opts.secret, flagSecret, "", "Secret for node acceptance")
|
||||
flags.StringVar(&opts.CACertHash, "ca-hash", "", "Hash of the Root Certificate Authority certificate used for trusted join")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "leave",
|
||||
Short: "Leave a Swarm.",
|
||||
Short: "Leave a Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLeave(dockerCli, opts)
|
||||
@@ -39,6 +39,6 @@ func runLeave(dockerCli *client.DockerCli, opts leaveOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(dockerCli.Out(), "Node left the default swarm.")
|
||||
fmt.Fprintln(dockerCli.Out(), "Node left the swarm.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,27 +1,49 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultListenAddr = "0.0.0.0:2377"
|
||||
// WORKER constant for worker name
|
||||
WORKER = "WORKER"
|
||||
// MANAGER constant for manager name
|
||||
MANAGER = "MANAGER"
|
||||
|
||||
worker = "WORKER"
|
||||
manager = "MANAGER"
|
||||
none = "NONE"
|
||||
|
||||
flagAutoAccept = "auto-accept"
|
||||
flagCertExpiry = "cert-expiry"
|
||||
flagDispatcherHeartbeat = "dispatcher-heartbeat"
|
||||
flagListenAddr = "listen-addr"
|
||||
flagSecret = "secret"
|
||||
flagTaskHistoryLimit = "task-history-limit"
|
||||
flagExternalCA = "external-ca"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultPolicies = []swarm.Policy{
|
||||
{Role: WORKER, Autoaccept: true},
|
||||
{Role: MANAGER, Autoaccept: false},
|
||||
{Role: worker, Autoaccept: true},
|
||||
{Role: manager, Autoaccept: false},
|
||||
}
|
||||
)
|
||||
|
||||
type swarmOptions struct {
|
||||
autoAccept AutoAcceptOption
|
||||
secret string
|
||||
taskHistoryLimit int64
|
||||
dispatcherHeartbeat time.Duration
|
||||
nodeCertExpiry time.Duration
|
||||
externalCA ExternalCAOption
|
||||
}
|
||||
|
||||
// NodeAddrOption is a pflag.Value for listen and remote addresses
|
||||
type NodeAddrOption struct {
|
||||
addr string
|
||||
@@ -29,21 +51,16 @@ type NodeAddrOption struct {
|
||||
|
||||
// String prints the representation of this flag
|
||||
func (a *NodeAddrOption) String() string {
|
||||
return a.addr
|
||||
return a.Value()
|
||||
}
|
||||
|
||||
// Set the value for this flag
|
||||
func (a *NodeAddrOption) Set(value string) error {
|
||||
if !strings.Contains(value, ":") {
|
||||
return fmt.Errorf("Invalud url, a host and port are required")
|
||||
addr, err := opts.ParseTCPAddr(value, a.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Invalud url, too many colons")
|
||||
}
|
||||
|
||||
a.addr = value
|
||||
a.addr = addr
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,47 +69,50 @@ func (a *NodeAddrOption) Type() string {
|
||||
return "node-addr"
|
||||
}
|
||||
|
||||
// Value returns the value of this option as addr:port
|
||||
func (a *NodeAddrOption) Value() string {
|
||||
return strings.TrimPrefix(a.addr, "tcp://")
|
||||
}
|
||||
|
||||
// NewNodeAddrOption returns a new node address option
|
||||
func NewNodeAddrOption() NodeAddrOption {
|
||||
return NodeAddrOption{addr: defaultListenAddr}
|
||||
func NewNodeAddrOption(addr string) NodeAddrOption {
|
||||
return NodeAddrOption{addr}
|
||||
}
|
||||
|
||||
// NewListenAddrOption returns a NodeAddrOption with default values
|
||||
func NewListenAddrOption() NodeAddrOption {
|
||||
return NewNodeAddrOption(defaultListenAddr)
|
||||
}
|
||||
|
||||
// AutoAcceptOption is a value type for auto-accept policy
|
||||
type AutoAcceptOption struct {
|
||||
values map[string]bool
|
||||
values map[string]struct{}
|
||||
}
|
||||
|
||||
// String prints a string representation of this option
|
||||
func (o *AutoAcceptOption) String() string {
|
||||
keys := []string{}
|
||||
for key := range o.values {
|
||||
keys = append(keys, key)
|
||||
keys = append(keys, fmt.Sprintf("%s=true", strings.ToLower(key)))
|
||||
}
|
||||
return strings.Join(keys, " ")
|
||||
return strings.Join(keys, ", ")
|
||||
}
|
||||
|
||||
// Set sets a new value on this option
|
||||
func (o *AutoAcceptOption) Set(value string) error {
|
||||
value = strings.ToUpper(value)
|
||||
switch value {
|
||||
case "", "NONE":
|
||||
if accept, ok := o.values[WORKER]; ok && accept {
|
||||
return fmt.Errorf("value NONE is incompatible with %s", WORKER)
|
||||
func (o *AutoAcceptOption) Set(acceptValues string) error {
|
||||
for _, value := range strings.Split(acceptValues, ",") {
|
||||
value = strings.ToUpper(value)
|
||||
switch value {
|
||||
case none, worker, manager:
|
||||
o.values[value] = struct{}{}
|
||||
default:
|
||||
return fmt.Errorf("must be one / combination of %s, %s; or NONE", worker, manager)
|
||||
}
|
||||
if accept, ok := o.values[MANAGER]; ok && accept {
|
||||
return fmt.Errorf("value NONE is incompatible with %s", MANAGER)
|
||||
}
|
||||
o.values[WORKER] = false
|
||||
o.values[MANAGER] = false
|
||||
case WORKER, MANAGER:
|
||||
if accept, ok := o.values[value]; ok && !accept {
|
||||
return fmt.Errorf("value NONE is incompatible with %s", value)
|
||||
}
|
||||
o.values[value] = true
|
||||
default:
|
||||
return fmt.Errorf("must be one of %s, %s, NONE", WORKER, MANAGER)
|
||||
}
|
||||
|
||||
// NONE must stand alone, so if any non-NONE setting exist with it, error with conflict
|
||||
if o.isPresent(none) && len(o.values) > 1 {
|
||||
return fmt.Errorf("value NONE cannot be specified alongside other node types")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -102,11 +122,15 @@ func (o *AutoAcceptOption) Type() string {
|
||||
}
|
||||
|
||||
// Policies returns a representation of this option for the api
|
||||
func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
|
||||
func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy {
|
||||
policies := []swarm.Policy{}
|
||||
for _, p := range defaultPolicies {
|
||||
if len(o.values) != 0 {
|
||||
p.Autoaccept = o.values[string(p.Role)]
|
||||
if _, ok := o.values[string(p.Role)]; ok {
|
||||
p.Autoaccept = true
|
||||
} else {
|
||||
p.Autoaccept = false
|
||||
}
|
||||
}
|
||||
p.Secret = secret
|
||||
policies = append(policies, p)
|
||||
@@ -114,7 +138,125 @@ func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
|
||||
return policies
|
||||
}
|
||||
|
||||
// isPresent returns whether the key exists in the set or not
|
||||
func (o *AutoAcceptOption) isPresent(key string) bool {
|
||||
_, c := o.values[key]
|
||||
return c
|
||||
}
|
||||
|
||||
// NewAutoAcceptOption returns a new auto-accept option
|
||||
func NewAutoAcceptOption() AutoAcceptOption {
|
||||
return AutoAcceptOption{values: make(map[string]bool)}
|
||||
return AutoAcceptOption{values: make(map[string]struct{})}
|
||||
}
|
||||
|
||||
// ExternalCAOption is a Value type for parsing external CA specifications.
|
||||
type ExternalCAOption struct {
|
||||
values []*swarm.ExternalCA
|
||||
}
|
||||
|
||||
// Set parses an external CA option.
|
||||
func (m *ExternalCAOption) Set(value string) error {
|
||||
parsed, err := parseExternalCA(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.values = append(m.values, parsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type returns the type of this option.
|
||||
func (m *ExternalCAOption) Type() string {
|
||||
return "external-ca"
|
||||
}
|
||||
|
||||
// String returns a string repr of this option.
|
||||
func (m *ExternalCAOption) String() string {
|
||||
externalCAs := []string{}
|
||||
for _, externalCA := range m.values {
|
||||
repr := fmt.Sprintf("%s: %s", externalCA.Protocol, externalCA.URL)
|
||||
externalCAs = append(externalCAs, repr)
|
||||
}
|
||||
return strings.Join(externalCAs, ", ")
|
||||
}
|
||||
|
||||
// Value returns the external CAs
|
||||
func (m *ExternalCAOption) Value() []*swarm.ExternalCA {
|
||||
return m.values
|
||||
}
|
||||
|
||||
// parseExternalCA parses an external CA specification from the command line,
|
||||
// such as protocol=cfssl,url=https://example.com.
|
||||
func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
|
||||
csvReader := csv.NewReader(strings.NewReader(caSpec))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
externalCA := swarm.ExternalCA{
|
||||
Options: make(map[string]string),
|
||||
}
|
||||
|
||||
var (
|
||||
hasProtocol bool
|
||||
hasURL bool
|
||||
)
|
||||
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
switch strings.ToLower(key) {
|
||||
case "protocol":
|
||||
hasProtocol = true
|
||||
if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
|
||||
externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
|
||||
} else {
|
||||
return nil, fmt.Errorf("unrecognized external CA protocol %s", value)
|
||||
}
|
||||
case "url":
|
||||
hasURL = true
|
||||
externalCA.URL = value
|
||||
default:
|
||||
externalCA.Options[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if !hasProtocol {
|
||||
return nil, errors.New("the external-ca option needs a protocol= parameter")
|
||||
}
|
||||
if !hasURL {
|
||||
return nil, errors.New("the external-ca option needs a url= parameter")
|
||||
}
|
||||
|
||||
return &externalCA, nil
|
||||
}
|
||||
|
||||
func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
||||
flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)")
|
||||
flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to join a cluster")
|
||||
flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit")
|
||||
flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
||||
flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
|
||||
flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
|
||||
}
|
||||
|
||||
func (opts *swarmOptions) ToSpec() swarm.Spec {
|
||||
spec := swarm.Spec{}
|
||||
if opts.secret != "" {
|
||||
spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret)
|
||||
} else {
|
||||
spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil)
|
||||
}
|
||||
spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
|
||||
spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
|
||||
spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
|
||||
spec.CAConfig.ExternalCAs = opts.externalCA.Value()
|
||||
return spec
|
||||
}
|
||||
|
||||
136
api/client/swarm/opts_test.go
Normal file
136
api/client/swarm/opts_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
)
|
||||
|
||||
func TestNodeAddrOptionSetHostAndPort(t *testing.T) {
|
||||
opt := NewNodeAddrOption("old:123")
|
||||
addr := "newhost:5555"
|
||||
assert.NilError(t, opt.Set(addr))
|
||||
assert.Equal(t, opt.Value(), addr)
|
||||
}
|
||||
|
||||
func TestNodeAddrOptionSetHostOnly(t *testing.T) {
|
||||
opt := NewListenAddrOption()
|
||||
assert.NilError(t, opt.Set("newhost"))
|
||||
assert.Equal(t, opt.Value(), "newhost:2377")
|
||||
}
|
||||
|
||||
func TestNodeAddrOptionSetHostOnlyIPv6(t *testing.T) {
|
||||
opt := NewListenAddrOption()
|
||||
assert.NilError(t, opt.Set("::1"))
|
||||
assert.Equal(t, opt.Value(), "[::1]:2377")
|
||||
}
|
||||
|
||||
func TestNodeAddrOptionSetPortOnly(t *testing.T) {
|
||||
opt := NewListenAddrOption()
|
||||
assert.NilError(t, opt.Set(":4545"))
|
||||
assert.Equal(t, opt.Value(), "0.0.0.0:4545")
|
||||
}
|
||||
|
||||
func TestNodeAddrOptionSetInvalidFormat(t *testing.T) {
|
||||
opt := NewListenAddrOption()
|
||||
assert.Error(t, opt.Set("http://localhost:4545"), "Invalid")
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetWorker(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("worker"))
|
||||
assert.Equal(t, opt.isPresent(worker), true)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetManager(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("manager"))
|
||||
assert.Equal(t, opt.isPresent(manager), true)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetInvalid(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("bogus"), "must be one / combination")
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetEmpty(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set(""), "must be one / combination")
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetNone(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("none"))
|
||||
assert.Equal(t, opt.isPresent(manager), false)
|
||||
assert.Equal(t, opt.isPresent(worker), false)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetTwo(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("worker,manager"))
|
||||
assert.Equal(t, opt.isPresent(manager), true)
|
||||
assert.Equal(t, opt.isPresent(worker), true)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetConflict(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("none,manager"), "value NONE cannot be specified alongside other node types")
|
||||
|
||||
opt = NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("none,worker"), "value NONE cannot be specified alongside other node types")
|
||||
|
||||
opt = NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("worker,none,manager"), "value NONE cannot be specified alongside other node types")
|
||||
|
||||
opt = NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("worker,manager,none"), "value NONE cannot be specified alongside other node types")
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionPoliciesDefault(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
secret := "thesecret"
|
||||
|
||||
policies := opt.Policies(&secret)
|
||||
assert.Equal(t, len(policies), 2)
|
||||
assert.Equal(t, policies[0], swarm.Policy{
|
||||
Role: worker,
|
||||
Autoaccept: true,
|
||||
Secret: &secret,
|
||||
})
|
||||
assert.Equal(t, policies[1], swarm.Policy{
|
||||
Role: manager,
|
||||
Autoaccept: false,
|
||||
Secret: &secret,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionPoliciesWithManager(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
secret := "thesecret"
|
||||
|
||||
assert.NilError(t, opt.Set("manager"))
|
||||
|
||||
policies := opt.Policies(&secret)
|
||||
assert.Equal(t, len(policies), 2)
|
||||
assert.Equal(t, policies[0], swarm.Policy{
|
||||
Role: worker,
|
||||
Autoaccept: false,
|
||||
Secret: &secret,
|
||||
})
|
||||
assert.Equal(t, policies[1], swarm.Policy{
|
||||
Role: manager,
|
||||
Autoaccept: true,
|
||||
Secret: &secret,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionString(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("manager"))
|
||||
assert.NilError(t, opt.Set("worker"))
|
||||
|
||||
repr := opt.String()
|
||||
assert.Contains(t, repr, "worker=true")
|
||||
assert.Contains(t, repr, "manager=true")
|
||||
}
|
||||
19
api/client/swarm/secret.go
Normal file
19
api/client/swarm/secret.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func generateRandomSecret() string {
|
||||
var secretBytes [generatedSecretEntropyBytes]byte
|
||||
|
||||
if _, err := cryptorand.Read(secretBytes[:]); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
||||
}
|
||||
|
||||
var nn big.Int
|
||||
nn.SetBytes(secretBytes[:])
|
||||
return fmt.Sprintf("%0[1]*s", maxGeneratedSecretLength, nn.Text(generatedSecretBase))
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
@@ -13,35 +12,23 @@ import (
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type updateOptions struct {
|
||||
autoAccept AutoAcceptOption
|
||||
secret string
|
||||
taskHistoryLimit int64
|
||||
dispatcherHeartbeat time.Duration
|
||||
}
|
||||
|
||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
opts := updateOptions{autoAccept: NewAutoAcceptOption()}
|
||||
var flags *pflag.FlagSet
|
||||
opts := swarmOptions{autoAccept: NewAutoAcceptOption()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update the Swarm.",
|
||||
Short: "Update the Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUpdate(dockerCli, flags, opts)
|
||||
return runUpdate(dockerCli, cmd.Flags(), opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags = cmd.Flags()
|
||||
flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager or none)")
|
||||
flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
|
||||
flags.Int64Var(&opts.taskHistoryLimit, "task-history-limit", 10, "Task history retention limit")
|
||||
flags.DurationVar(&opts.dispatcherHeartbeat, "dispatcher-heartbeat", time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
||||
addSwarmFlags(cmd.Flags(), &opts)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOptions) error {
|
||||
func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts swarmOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -54,43 +41,55 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOpt
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println("Swarm updated.")
|
||||
fmt.Fprintln(dockerCli.Out(), "Swarm updated.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
|
||||
spec := &swarm.Spec
|
||||
|
||||
if flags.Changed("auto-accept") {
|
||||
value := flags.Lookup("auto-accept").Value.(*AutoAcceptOption)
|
||||
if len(spec.AcceptancePolicy.Policies) > 0 {
|
||||
spec.AcceptancePolicy.Policies = value.Policies(spec.AcceptancePolicy.Policies[0].Secret)
|
||||
} else {
|
||||
spec.AcceptancePolicy.Policies = value.Policies("")
|
||||
}
|
||||
if flags.Changed(flagAutoAccept) {
|
||||
value := flags.Lookup(flagAutoAccept).Value.(*AutoAcceptOption)
|
||||
spec.AcceptancePolicy.Policies = value.Policies(nil)
|
||||
}
|
||||
|
||||
if flags.Changed("secret") {
|
||||
secret, _ := flags.GetString("secret")
|
||||
for _, policy := range spec.AcceptancePolicy.Policies {
|
||||
policy.Secret = secret
|
||||
}
|
||||
var psecret *string
|
||||
if flags.Changed(flagSecret) {
|
||||
secret, _ := flags.GetString(flagSecret)
|
||||
psecret = &secret
|
||||
}
|
||||
|
||||
if flags.Changed("task-history-limit") {
|
||||
spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64("task-history-limit")
|
||||
for i := range spec.AcceptancePolicy.Policies {
|
||||
spec.AcceptancePolicy.Policies[i].Secret = psecret
|
||||
}
|
||||
|
||||
if flags.Changed("dispatcher-heartbeat") {
|
||||
if v, err := flags.GetDuration("dispatcher-heartbeat"); err == nil {
|
||||
if flags.Changed(flagTaskHistoryLimit) {
|
||||
spec.Orchestration.TaskHistoryRetentionLimit, _ = flags.GetInt64(flagTaskHistoryLimit)
|
||||
}
|
||||
|
||||
if flags.Changed(flagDispatcherHeartbeat) {
|
||||
if v, err := flags.GetDuration(flagDispatcherHeartbeat); err == nil {
|
||||
spec.Dispatcher.HeartbeatPeriod = uint64(v.Nanoseconds())
|
||||
}
|
||||
}
|
||||
|
||||
if flags.Changed(flagCertExpiry) {
|
||||
if v, err := flags.GetDuration(flagCertExpiry); err == nil {
|
||||
spec.CAConfig.NodeCertExpiry = v
|
||||
}
|
||||
}
|
||||
|
||||
if flags.Changed(flagExternalCA) {
|
||||
value := flags.Lookup(flagExternalCA).Value.(*ExternalCAOption)
|
||||
spec.CAConfig.ExternalCAs = value.Value()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s\t%s\t%s\n"
|
||||
psTaskItemFmt = "%s\t%s\t%s\t%s\t%s %s ago\t%s\t%s\n"
|
||||
)
|
||||
|
||||
type tasksBySlot []swarm.Task
|
||||
@@ -69,7 +69,7 @@ func Print(dockerCli *client.DockerCli, ctx context.Context, tasks []swarm.Task,
|
||||
serviceValue,
|
||||
task.Spec.ContainerSpec.Image,
|
||||
client.PrettyPrint(task.Status.State),
|
||||
units.HumanDuration(time.Since(task.Status.Timestamp)),
|
||||
strings.ToLower(units.HumanDuration(time.Since(task.Status.Timestamp))),
|
||||
client.PrettyPrint(task.DesiredState),
|
||||
nodeValue,
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ func CopyToFile(outfile string, r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ForwardAllSignals forwards signals to the contianer
|
||||
// ForwardAllSignals forwards signals to the container
|
||||
// TODO: this can be unexported again once all container commands are under
|
||||
// api/client/container
|
||||
func (cli *DockerCli) ForwardAllSignals(ctx context.Context, cid string) chan os.Signal {
|
||||
|
||||
@@ -19,7 +19,7 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] VOLUME [VOLUME...]",
|
||||
Short: "Return low-level information on a volume",
|
||||
Short: "Display detailed information on one or more volumes",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.names = args
|
||||
|
||||
@@ -33,7 +33,7 @@ func runRemove(dockerCli *client.DockerCli, volumes []string) error {
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Err(), "%s\n", name)
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
||||
}
|
||||
|
||||
if status != 0 {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/libtrust"
|
||||
@@ -135,7 +139,11 @@ func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error generating key: %s", err)
|
||||
}
|
||||
if err := libtrust.SaveKey(trustKeyPath, trustKey); err != nil {
|
||||
encodedKey, err := serializePrivateKey(trustKey, filepath.Ext(trustKeyPath))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error serializing key: %s", err)
|
||||
}
|
||||
if err := ioutils.AtomicWriteFile(trustKeyPath, encodedKey, os.FileMode(0600)); err != nil {
|
||||
return nil, fmt.Errorf("Error saving key file: %s", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
@@ -143,3 +151,19 @@ func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) {
|
||||
}
|
||||
return trustKey, nil
|
||||
}
|
||||
|
||||
func serializePrivateKey(key libtrust.PrivateKey, ext string) (encoded []byte, err error) {
|
||||
if ext == ".json" || ext == ".jwk" {
|
||||
encoded, err = json.Marshal(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to encode private key JWK: %s", err)
|
||||
}
|
||||
} else {
|
||||
pemBlock, err := key.PEMBlock()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to encode private key PEM: %s", err)
|
||||
}
|
||||
encoded = pem.EncodeToMemory(pemBlock)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
@@ -40,9 +41,7 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
|
||||
|
||||
var postForm map[string]interface{}
|
||||
if err := json.Unmarshal(b, &postForm); err == nil {
|
||||
if _, exists := postForm["password"]; exists {
|
||||
postForm["password"] = "*****"
|
||||
}
|
||||
maskSecretKeys(postForm)
|
||||
formStr, errMarshal := json.Marshal(postForm)
|
||||
if errMarshal == nil {
|
||||
logrus.Debugf("form data: %s", string(formStr))
|
||||
@@ -54,3 +53,24 @@ func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWri
|
||||
return handler(ctx, w, r, vars)
|
||||
}
|
||||
}
|
||||
|
||||
func maskSecretKeys(inp interface{}) {
|
||||
if arr, ok := inp.([]interface{}); ok {
|
||||
for _, f := range arr {
|
||||
maskSecretKeys(f)
|
||||
}
|
||||
return
|
||||
}
|
||||
if form, ok := inp.(map[string]interface{}); ok {
|
||||
loop0:
|
||||
for k, v := range form {
|
||||
for _, m := range []string{"password", "secret"} {
|
||||
if strings.EqualFold(m, k) {
|
||||
form[k] = "*****"
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
maskSecretKeys(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,17 +32,17 @@ type copyBackend interface {
|
||||
|
||||
// stateBackend includes functions to implement to provide container state lifecycle functionality.
|
||||
type stateBackend interface {
|
||||
ContainerCreate(types.ContainerCreateConfig) (types.ContainerCreateResponse, error)
|
||||
ContainerCreate(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
|
||||
ContainerKill(name string, sig uint64) error
|
||||
ContainerPause(name string) error
|
||||
ContainerRename(oldName, newName string) error
|
||||
ContainerResize(name string, height, width int) error
|
||||
ContainerRestart(name string, seconds int) error
|
||||
ContainerRm(name string, config *types.ContainerRmConfig) error
|
||||
ContainerStart(name string, hostConfig *container.HostConfig) error
|
||||
ContainerStart(name string, hostConfig *container.HostConfig, validateHostname bool) error
|
||||
ContainerStop(name string, seconds int) error
|
||||
ContainerUnpause(name string) error
|
||||
ContainerUpdate(name string, hostConfig *container.HostConfig) ([]string, error)
|
||||
ContainerUpdate(name string, hostConfig *container.HostConfig, validateHostname bool) ([]string, error)
|
||||
ContainerWait(name string, timeout time.Duration) (int, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -132,10 +132,10 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
|
||||
// including r.TransferEncoding
|
||||
// allow a nil body for backwards compatibility
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
var hostConfig *container.HostConfig
|
||||
// A non-nil json object is at least 7 characters.
|
||||
if r.ContentLength > 7 || r.ContentLength == -1 {
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
if versions.GreaterThanOrEqualTo(version, "1.24") {
|
||||
return validationError{fmt.Errorf("starting container with HostConfig was deprecated since v1.10 and removed in v1.12")}
|
||||
}
|
||||
@@ -151,7 +151,8 @@ func (s *containerRouter) postContainersStart(ctx context.Context, w http.Respon
|
||||
hostConfig = c
|
||||
}
|
||||
|
||||
if err := s.backend.ContainerStart(vars["name"], hostConfig); err != nil {
|
||||
validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
|
||||
if err := s.backend.ContainerStart(vars["name"], hostConfig, validateHostname); err != nil {
|
||||
return err
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
@@ -311,6 +312,7 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
|
||||
return err
|
||||
}
|
||||
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
var updateConfig container.UpdateConfig
|
||||
|
||||
decoder := json.NewDecoder(r.Body)
|
||||
@@ -324,7 +326,8 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
|
||||
}
|
||||
|
||||
name := vars["name"]
|
||||
warnings, err := s.backend.ContainerUpdate(name, hostConfig)
|
||||
validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
|
||||
warnings, err := s.backend.ContainerUpdate(name, hostConfig, validateHostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -351,13 +354,14 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
|
||||
version := httputils.VersionFromContext(ctx)
|
||||
adjustCPUShares := versions.LessThan(version, "1.19")
|
||||
|
||||
validateHostname := versions.GreaterThanOrEqualTo(version, "1.24")
|
||||
ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
|
||||
Name: name,
|
||||
Config: config,
|
||||
HostConfig: hostConfig,
|
||||
NetworkingConfig: networkingConfig,
|
||||
AdjustCPUShares: adjustCPUShares,
|
||||
})
|
||||
}, validateHostname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func filterNetworks(nws []types.NetworkResource, filter filters.Args) ([]types.N
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var displayNet []types.NetworkResource
|
||||
displayNet := []types.NetworkResource{}
|
||||
for _, nw := range nws {
|
||||
if filter.Include("driver") {
|
||||
if !filter.ExactMatch("driver", nw.Driver) {
|
||||
|
||||
@@ -81,6 +81,10 @@ func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWr
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := n.clusterProvider.GetNetwork(create.Name); err == nil {
|
||||
return libnetwork.NetworkNameError(create.Name)
|
||||
}
|
||||
|
||||
nw, err := n.backend.CreateNetwork(create)
|
||||
if err != nil {
|
||||
if _, ok := err.(libnetwork.ManagerRedirectError); !ok {
|
||||
|
||||
@@ -14,8 +14,8 @@ type Backend interface {
|
||||
Update(uint64, types.Spec) error
|
||||
GetServices(basictypes.ServiceListOptions) ([]types.Service, error)
|
||||
GetService(string) (types.Service, error)
|
||||
CreateService(types.ServiceSpec) (string, error)
|
||||
UpdateService(string, uint64, types.ServiceSpec) error
|
||||
CreateService(types.ServiceSpec, string) (string, error)
|
||||
UpdateService(string, uint64, types.ServiceSpec, string) error
|
||||
RemoveService(string) error
|
||||
GetNodes(basictypes.NodeListOptions) ([]types.Node, error)
|
||||
GetNode(string) (types.Node, error)
|
||||
|
||||
@@ -107,9 +107,12 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := sr.backend.CreateService(service)
|
||||
// Get returns "" if the header does not exist
|
||||
encodedAuth := r.Header.Get("X-Registry-Auth")
|
||||
|
||||
id, err := sr.backend.CreateService(service, encodedAuth)
|
||||
if err != nil {
|
||||
logrus.Errorf("Error reating service %s: %v", id, err)
|
||||
logrus.Errorf("Error creating service %s: %v", id, err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -130,7 +133,10 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
|
||||
return fmt.Errorf("Invalid service version '%s': %s", rawVersion, err.Error())
|
||||
}
|
||||
|
||||
if err := sr.backend.UpdateService(vars["id"], version, service); err != nil {
|
||||
// Get returns "" if the header does not exist
|
||||
encodedAuth := r.Header.Get("X-Registry-Auth")
|
||||
|
||||
if err := sr.backend.UpdateService(vars["id"], version, service, encodedAuth); err != nil {
|
||||
logrus.Errorf("Error updating service %s: %v", vars["id"], err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ type Backend interface {
|
||||
// ContainerAttachRaw attaches to container.
|
||||
ContainerAttachRaw(cID string, stdin io.ReadCloser, stdout, stderr io.Writer, stream bool) error
|
||||
// ContainerCreate creates a new Docker container and returns potential warnings
|
||||
ContainerCreate(types.ContainerCreateConfig) (types.ContainerCreateResponse, error)
|
||||
ContainerCreate(config types.ContainerCreateConfig, validateHostname bool) (types.ContainerCreateResponse, error)
|
||||
// ContainerRm removes a container specified by `id`.
|
||||
ContainerRm(name string, config *types.ContainerRmConfig) error
|
||||
// Commit creates a new Docker image from an existing Docker container.
|
||||
@@ -124,7 +124,7 @@ type Backend interface {
|
||||
// ContainerKill stops the container execution abruptly.
|
||||
ContainerKill(containerID string, sig uint64) error
|
||||
// ContainerStart starts a new container
|
||||
ContainerStart(containerID string, hostConfig *container.HostConfig) error
|
||||
ContainerStart(containerID string, hostConfig *container.HostConfig, validateHostname bool) error
|
||||
// ContainerWait stops processing until the given container is stopped.
|
||||
ContainerWait(containerID string, timeout time.Duration) (int, error)
|
||||
// ContainerUpdateCmdOnBuild updates container.Path and container.Args
|
||||
|
||||
@@ -172,7 +172,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
|
||||
}
|
||||
|
||||
cmd := b.runConfig.Cmd
|
||||
b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), "#(nop) %s %s in %s ", cmdName, srcHash, dest))
|
||||
b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) %s %s in %s ", cmdName, srcHash, dest)))
|
||||
defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
|
||||
|
||||
if hit, err := b.probeCache(); err != nil {
|
||||
@@ -181,7 +181,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD
|
||||
return nil
|
||||
}
|
||||
|
||||
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
|
||||
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -421,7 +421,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
|
||||
fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word)
|
||||
}
|
||||
|
||||
// Copy the ONBUILD triggers, and remove them from the config, since the config will be committed.
|
||||
// Copy the ONBUILD triggers, and remove them from the config, since the config will be comitted.
|
||||
onBuildTriggers := b.runConfig.OnBuild
|
||||
b.runConfig.OnBuild = []string{}
|
||||
|
||||
@@ -508,7 +508,7 @@ func (b *Builder) create() (string, error) {
|
||||
c, err := b.docker.ContainerCreate(types.ContainerCreateConfig{
|
||||
Config: b.runConfig,
|
||||
HostConfig: hostConfig,
|
||||
})
|
||||
}, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -552,7 +552,7 @@ func (b *Builder) run(cID string) (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := b.docker.ContainerStart(cID, nil); err != nil {
|
||||
if err := b.docker.ContainerStart(cID, nil, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
17
cli/error.go
17
cli/error.go
@@ -1,21 +1,20 @@
|
||||
package cli
|
||||
|
||||
import "bytes"
|
||||
import "strings"
|
||||
|
||||
// Errors is a list of errors.
|
||||
// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
|
||||
// all the errors that happened during the loop.
|
||||
type Errors []error
|
||||
|
||||
func (errs Errors) Error() string {
|
||||
if len(errs) < 1 {
|
||||
func (errList Errors) Error() string {
|
||||
if len(errList) < 1 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(errs[0].Error())
|
||||
for _, err := range errs[1:] {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(err.Error())
|
||||
|
||||
out := make([]string, len(errList))
|
||||
for i := range errList {
|
||||
out[i] = errList[i].Error()
|
||||
}
|
||||
return buf.String()
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NoArgs validate args and returns an error if there are any args
|
||||
// NoArgs validates args and returns an error if there are any args
|
||||
func NoArgs(cmd *cobra.Command, args []string) error {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -10,5 +10,5 @@ import (
|
||||
)
|
||||
|
||||
func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
|
||||
return plugin.Init(config.Root, config.ExecRoot, remote, rs)
|
||||
return plugin.Init(config.Root, config.ExecRoot, remote, rs, config.LiveRestore)
|
||||
}
|
||||
|
||||
@@ -823,7 +823,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
|
||||
})
|
||||
}
|
||||
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs))
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs, svcCfg.Aliases[n.ID()]))
|
||||
}
|
||||
|
||||
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
||||
|
||||
@@ -54,7 +54,8 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
|
||||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []Mount {
|
||||
return nil
|
||||
var mounts []Mount
|
||||
return mounts
|
||||
}
|
||||
|
||||
// UpdateContainer updates configuration of a container
|
||||
|
||||
@@ -195,7 +195,7 @@ func (s *State) ExitCode() int {
|
||||
return res
|
||||
}
|
||||
|
||||
// SetExitCode set current exitcode for the state. Take lock before if state
|
||||
// SetExitCode sets current exitcode for the state. Take lock before if state
|
||||
// may be shared.
|
||||
func (s *State) SetExitCode(ec int) {
|
||||
s.exitCode = ec
|
||||
@@ -214,7 +214,7 @@ func (s *State) SetRunning(pid int, initial bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetStoppedLocking locks the container state is sets it to "stopped".
|
||||
// SetStoppedLocking locks the container state and sets it to "stopped".
|
||||
func (s *State) SetStoppedLocking(exitStatus *ExitStatus) {
|
||||
s.Lock()
|
||||
s.SetStopped(exitStatus)
|
||||
@@ -290,7 +290,7 @@ func (s *State) SetRemovalInProgress() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ResetRemovalInProgress make the RemovalInProgress state to false.
|
||||
// ResetRemovalInProgress makes the RemovalInProgress state to false.
|
||||
func (s *State) ResetRemovalInProgress() {
|
||||
s.Lock()
|
||||
s.RemovalInProgress = false
|
||||
|
||||
18
contrib/builder/rpm/amd64/fedora-24/Dockerfile
Normal file
18
contrib/builder/rpm/amd64/fedora-24/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/amd64/generate.sh"!
|
||||
#
|
||||
|
||||
FROM fedora:24
|
||||
|
||||
RUN dnf install -y @development-tools fedora-packager
|
||||
RUN dnf install -y btrfs-progs-devel device-mapper-devel glibc-static libseccomp-devel libselinux-devel libtool-ltdl-devel pkgconfig selinux-policy selinux-policy-devel sqlite-devel systemd-devel tar git
|
||||
|
||||
ENV GO_VERSION 1.6.2
|
||||
RUN curl -fSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xzC /usr/local
|
||||
ENV PATH $PATH:/usr/local/go/bin
|
||||
|
||||
ENV AUTO_GOPATH 1
|
||||
|
||||
ENV DOCKER_BUILDTAGS pkcs11 seccomp selinux
|
||||
ENV RUNC_BUILDTAGS seccomp selinux
|
||||
|
||||
@@ -232,6 +232,7 @@ flags=(
|
||||
CGROUP_HUGETLB
|
||||
NET_CLS_CGROUP $netprio
|
||||
CFS_BANDWIDTH FAIR_GROUP_SCHED RT_GROUP_SCHED
|
||||
IP_VS
|
||||
)
|
||||
check_flags "${flags[@]}"
|
||||
|
||||
@@ -249,6 +250,8 @@ echo '- Network Drivers:'
|
||||
{
|
||||
echo '- "'$(wrap_color 'overlay' blue)'":'
|
||||
check_flags VXLAN | sed 's/^/ /'
|
||||
echo ' Optional (for secure networks):'
|
||||
check_flags XFRM_ALGO XFRM_USER | sed 's/^/ /'
|
||||
} | sed 's/^/ /'
|
||||
|
||||
echo '- Storage Drivers:'
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
# setting environment variables.
|
||||
#
|
||||
# DOCKER_COMPLETION_SHOW_NETWORK_IDS
|
||||
# DOCKER_COMPLETION_SHOW_NODE_IDS
|
||||
# DOCKER_COMPLETION_SHOW_SERVICE_IDS
|
||||
# "no" - Show names only (default)
|
||||
# "yes" - Show names and ids
|
||||
#
|
||||
@@ -197,53 +199,84 @@ __docker_complete_runtimes() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_runtimes)" -- "$cur") )
|
||||
}
|
||||
|
||||
# Returns a list of all nodes. Additional arguments to `docker node`
|
||||
# may be specified in order to filter the node list, e.g.
|
||||
# `__docker_nodes --filter role=manager`
|
||||
# By default, only node names are completed.
|
||||
# Set DOCKER_COMPLETION_SHOW_NODE_IDS=yes to also complete node IDs.
|
||||
# An optional first argument `--id|--name` may be used to limit
|
||||
# the output to the IDs or names of matching nodes. This setting takes
|
||||
# precedence over the environment setting.
|
||||
__docker_nodes() {
|
||||
local fields='$1,$2' # node names & IDs
|
||||
__docker_q node ls | sed -e 's/\*//g' | awk "NR>1 {print $fields}"
|
||||
local fields='$2' # default: node name only
|
||||
[ "${DOCKER_COMPLETION_SHOW_NODE_IDS}" = yes ] && fields='$1,$2' # ID and name
|
||||
|
||||
if [ "$1" = "--id" ] ; then
|
||||
fields='$1' # IDs only
|
||||
shift
|
||||
elif [ "$1" = "--name" ] ; then
|
||||
fields='$2' # names only
|
||||
shift
|
||||
fi
|
||||
__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}"
|
||||
}
|
||||
|
||||
# Applies completion of nodes based on the current value of `$cur` or
|
||||
# the value of the optional first argument `--cur`, if given.
|
||||
# Additional filters may be appended, see `__docker_nodes`.
|
||||
__docker_complete_nodes() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_nodes $1)" -- "$cur") )
|
||||
local current=$cur
|
||||
if [ "$1" = "--cur" ] ; then
|
||||
current="$2"
|
||||
shift 2
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "$(__docker_nodes "$@")" -- "$current") )
|
||||
}
|
||||
|
||||
__docker_complete_nodes_plus_self() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_nodes $1) self" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_pending_nodes() {
|
||||
local fields='$1' # node ID
|
||||
__docker_q node ls --filter membership=pending | awk "NR>1 {print $fields}"
|
||||
}
|
||||
|
||||
__docker_complete_pending_nodes() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_pending_nodes $1)" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_manager_nodes() {
|
||||
local fields='$1,$2' # node names & IDs
|
||||
__docker_q node ls --filter role=manager | awk "NR>1 {print $fields}"
|
||||
}
|
||||
|
||||
__docker_complete_manager_nodes() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_manager_nodes $1)" -- "$cur") )
|
||||
}
|
||||
|
||||
__docker_worker_nodes() {
|
||||
local fields='$1,$2' # node names & IDs
|
||||
__docker_q node ls --filter role=worker | awk "NR>1 {print $fields}"
|
||||
}
|
||||
|
||||
__docker_complete_worker_nodes() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_worker_nodes $1)" -- "$cur") )
|
||||
__docker_complete_nodes "$@"
|
||||
COMPREPLY+=( self )
|
||||
}
|
||||
|
||||
# Returns a list of all services. Additional arguments to `docker service ls`
|
||||
# may be specified in order to filter the service list, e.g.
|
||||
# `__docker_services --filter name=xxx`
|
||||
# By default, only node names are completed.
|
||||
# Set DOCKER_COMPLETION_SHOW_SERVICE_IDS=yes to also complete service IDs.
|
||||
# An optional first argument `--id|--name` may be used to limit
|
||||
# the output to the IDs or names of matching services. This setting takes
|
||||
# precedence over the environment setting.
|
||||
__docker_services() {
|
||||
local fields='$1,$2' # service names & IDs
|
||||
__docker_q service ls | awk "NR>1 {print $fields}"
|
||||
local fields='$2' # default: service name only
|
||||
[ "${DOCKER_COMPLETION_SHOW_SERVICE_IDS}" = yes ] && fields='$1,$2' # ID & name
|
||||
|
||||
if [ "$1" = "--id" ] ; then
|
||||
fields='$1' # IDs only
|
||||
shift
|
||||
elif [ "$1" = "--name" ] ; then
|
||||
fields='$2' # names only
|
||||
shift
|
||||
fi
|
||||
__docker_q service ls "$@" | awk "NR>1 {print $fields}"
|
||||
}
|
||||
|
||||
# Applies completion of services based on the current value of `$cur` or
|
||||
# the value of the optional first argument `--cur`, if given.
|
||||
# Additional filters may be appended, see `__docker_services`.
|
||||
__docker_complete_services() {
|
||||
COMPREPLY=( $(compgen -W "$(__docker_services $1)" -- "$cur") )
|
||||
local current=$cur
|
||||
if [ "$1" = "--cur" ] ; then
|
||||
current="$2"
|
||||
shift 2
|
||||
fi
|
||||
COMPREPLY=( $(compgen -W "$(__docker_services "$@")" -- "$current") )
|
||||
}
|
||||
|
||||
# Appends the word passed as an argument to every word in `$COMPREPLY`.
|
||||
# Normally you do this with `compgen -S`. This function exists so that you can use
|
||||
# the __docker_complete_XXX functions in cases where you need a suffix.
|
||||
__docker_append_to_completions() {
|
||||
COMPREPLY=( ${COMPREPLY[@]/%/"$1"} )
|
||||
}
|
||||
|
||||
# Finds the position of the first word that is neither option nor an option's argument.
|
||||
@@ -693,7 +726,7 @@ _docker_build() {
|
||||
--cgroup-parent
|
||||
--cpuset-cpus
|
||||
--cpuset-mems
|
||||
--cpu-shares
|
||||
--cpu-shares -c
|
||||
--cpu-period
|
||||
--cpu-quota
|
||||
--file -f
|
||||
@@ -942,10 +975,11 @@ _docker_daemon() {
|
||||
return
|
||||
;;
|
||||
--storage-driver|-s)
|
||||
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
|
||||
COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) )
|
||||
return
|
||||
;;
|
||||
--storage-opt)
|
||||
local btrfs_options="btrfs.min_space"
|
||||
local devicemapper_options="
|
||||
dm.basesize
|
||||
dm.blkdiscard
|
||||
@@ -965,7 +999,10 @@ _docker_daemon() {
|
||||
|
||||
case $(__docker_value_of_option '--storage-driver|-s') in
|
||||
'')
|
||||
COMPREPLY=( $( compgen -W "$devicemapper_options $zfs_options" -S = -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "$btrfs_options $devicemapper_options $zfs_options" -S = -- "$cur" ) )
|
||||
;;
|
||||
btrfs)
|
||||
COMPREPLY=( $( compgen -W "$btrfs_options" -S = -- "$cur" ) )
|
||||
;;
|
||||
devicemapper)
|
||||
COMPREPLY=( $( compgen -W "$devicemapper_options" -S = -- "$cur" ) )
|
||||
@@ -1049,6 +1086,7 @@ _docker_events() {
|
||||
export
|
||||
import
|
||||
kill
|
||||
load
|
||||
mount
|
||||
oom
|
||||
pause
|
||||
@@ -1058,6 +1096,7 @@ _docker_events() {
|
||||
rename
|
||||
resize
|
||||
restart
|
||||
save
|
||||
start
|
||||
stop
|
||||
tag
|
||||
@@ -1355,6 +1394,7 @@ _docker_network_connect() {
|
||||
--ip
|
||||
--ip6
|
||||
--link
|
||||
--link-local-ip
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -1528,11 +1568,12 @@ _docker_network() {
|
||||
_docker_service() {
|
||||
local subcommands="
|
||||
create
|
||||
tasks
|
||||
inspect
|
||||
ls list
|
||||
rm remove
|
||||
scale
|
||||
tasks
|
||||
update
|
||||
ls
|
||||
rm
|
||||
"
|
||||
__docker_subcommands "$subcommands" && return
|
||||
|
||||
@@ -1547,43 +1588,61 @@ _docker_service() {
|
||||
}
|
||||
|
||||
_docker_service_create() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --constraint --endpoint-ingress --endpoint-mode --env --label --limit-cpu --limit-memory --mode --name --network --publish --reserve-cpu --reserve-memory --restart-condition --restart-delay --restart-max-attempts --restart-window --replicas --stop-grace-period --update-delay --update-parallelism --user --volume --workdir" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_update() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--arg --command --constraint --endpoint-ingress --endpoint-mode --env --help --image --label --limit-cpu --limit-memory --mode --name --network --publish --reserve-cpu --reserve-memory --restart-condition--restart-delay --restart-max-attempts --restart-window --replicas --stop-grace-period --update-delay --update-parallelism --user --volume --workdir" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
esac
|
||||
_docker_service_update
|
||||
}
|
||||
|
||||
_docker_service_inspect() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --format --pretty" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--format -f --help --pretty -p" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_tasks() {
|
||||
_docker_service_list() {
|
||||
_docker_service_ls
|
||||
}
|
||||
|
||||
_docker_service_ls() {
|
||||
local key=$(__docker_map_key_of_current_option '--filter|-f')
|
||||
case "$key" in
|
||||
id)
|
||||
__docker_complete_services --cur "${cur##*=}" --id
|
||||
return
|
||||
;;
|
||||
name)
|
||||
__docker_complete_services --cur "${cur##*=}" --name
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "id label name" -S = -- "$cur" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --all --filter --no-resolve" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--filter -f --help --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_remove() {
|
||||
_docker_service_rm
|
||||
}
|
||||
|
||||
_docker_service_rm() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
@@ -1594,21 +1653,150 @@ _docker_service_rm() {
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_ls() {
|
||||
_docker_service_scale() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
__docker_append_to_completions "="
|
||||
__docker_nospace
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_tasks() {
|
||||
local key=$(__docker_map_key_of_current_option '--filter|-f')
|
||||
case "$key" in
|
||||
desired-state)
|
||||
COMPREPLY=( $( compgen -W "accepted running" -- "${cur##*=}" ) )
|
||||
return
|
||||
;;
|
||||
name)
|
||||
__docker_complete_services --cur "${cur##*=}" --name
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "desired-state id name" -S = -- "$cur" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
local counter=$(__docker_pos_first_nonflag '--filter|-f')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_complete_services
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_update() {
|
||||
local $subcommand="${words[$subcommand_pos]}"
|
||||
|
||||
local options_with_args="
|
||||
--constraint
|
||||
--endpoint-mode
|
||||
--env -e
|
||||
--label -l
|
||||
--limit-cpu
|
||||
--limit-memory
|
||||
--mode
|
||||
--mount -m
|
||||
--name
|
||||
--network
|
||||
--publish -p
|
||||
--replicas
|
||||
--reserve-cpu
|
||||
--reserve-memory
|
||||
--restart-condition
|
||||
--restart-delay
|
||||
--restart-max-attempts
|
||||
--restart-window
|
||||
--stop-grace-period
|
||||
--update-delay
|
||||
--update-parallelism
|
||||
--user -u
|
||||
--workdir -w
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
--help
|
||||
"
|
||||
|
||||
if [ "$subcommand" = "update" ] ; then
|
||||
options_with_args="$options_with_args
|
||||
--arg
|
||||
--command
|
||||
--image
|
||||
"
|
||||
|
||||
case "$prev" in
|
||||
--image)
|
||||
__docker_complete_image_repos_and_tags
|
||||
return
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$prev" in
|
||||
--endpoint-mode)
|
||||
COMPREPLY=( $( compgen -W "DNSRR VIP" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--env|-e)
|
||||
COMPREPLY=( $( compgen -e -S = -- "$cur" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
--mode)
|
||||
COMPREPLY=( $( compgen -W "global replicated" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--network)
|
||||
__docker_complete_networks
|
||||
return
|
||||
;;
|
||||
--restart-condition)
|
||||
COMPREPLY=( $( compgen -W "any none on-failure" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--user|-u)
|
||||
__docker_complete_user_group
|
||||
return
|
||||
;;
|
||||
$(__docker_to_extglob "$options_with_args") )
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
if [ "$subcommand" = "update" ] ; then
|
||||
__docker_complete_services
|
||||
fi
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm() {
|
||||
local subcommands="
|
||||
init
|
||||
join
|
||||
update
|
||||
leave
|
||||
inspect
|
||||
join
|
||||
leave
|
||||
update
|
||||
"
|
||||
__docker_subcommands "$subcommands" && return
|
||||
|
||||
@@ -1623,25 +1811,62 @@ _docker_swarm() {
|
||||
}
|
||||
|
||||
_docker_swarm_init() {
|
||||
case "$prev" in
|
||||
--auto-accept)
|
||||
COMPREPLY=( $( compgen -W "manager none worker" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--listen-addr)
|
||||
if [[ $cur == *: ]] ; then
|
||||
COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
|
||||
fi
|
||||
return
|
||||
;;
|
||||
--secret)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --auto-accept --force-new-cluster --secret" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --force-new-cluster --help --listen-addr --secret" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_inspect() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--format -f --help" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_join() {
|
||||
case "$prev" in
|
||||
--ca-hash|--secret)
|
||||
return
|
||||
;;
|
||||
--listen-addr)
|
||||
if [[ $cur == *: ]] ; then
|
||||
COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
|
||||
fi
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--ca-hash --help --listen-addr --manager --secret" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_update() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
|
||||
*:)
|
||||
COMPREPLY=( $( compgen -W "2377" -- "${cur##*:}" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -1654,10 +1879,20 @@ _docker_swarm_leave() {
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_inspect() {
|
||||
_docker_swarm_update() {
|
||||
case "$prev" in
|
||||
--auto-accept)
|
||||
COMPREPLY=( $( compgen -W "manager none worker" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--cert-expiry|--dispatcher-heartbeat|--secret|--task-history-limit)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--format --help" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --cert-expiry --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -1667,9 +1902,9 @@ _docker_node() {
|
||||
accept
|
||||
demote
|
||||
inspect
|
||||
ls
|
||||
ls list
|
||||
promote
|
||||
rm
|
||||
rm remove
|
||||
tasks
|
||||
update
|
||||
"
|
||||
@@ -1691,24 +1926,64 @@ _docker_node_accept() {
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_pending_nodes
|
||||
__docker_complete_nodes --id --filter membership=pending
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_demote() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_nodes --filter role=manager
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_inspect() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --format --pretty" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--format -f --help --pretty -p" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_nodes
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_list() {
|
||||
_docker_node_ls
|
||||
}
|
||||
|
||||
_docker_node_ls() {
|
||||
local key=$(__docker_map_key_of_current_option '--filter|-f')
|
||||
case "$key" in
|
||||
id)
|
||||
__docker_complete_nodes --cur "${cur##*=}" --id
|
||||
return
|
||||
;;
|
||||
name)
|
||||
__docker_complete_nodes --cur "${cur##*=}" --name
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "id label name" -S = -- "$cur" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --filter --quiet" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--filter -f --help --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -1719,18 +1994,12 @@ _docker_node_promote() {
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_worker_nodes
|
||||
__docker_complete_nodes --filter role=worker
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_demote() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_manager_nodes
|
||||
esac
|
||||
_docker_node_remove() {
|
||||
_docker_node_rm
|
||||
}
|
||||
|
||||
_docker_node_rm() {
|
||||
@@ -1744,19 +2013,58 @@ _docker_node_rm() {
|
||||
}
|
||||
|
||||
_docker_node_tasks() {
|
||||
local key=$(__docker_map_key_of_current_option '--filter|-f')
|
||||
case "$key" in
|
||||
desired-state)
|
||||
COMPREPLY=( $( compgen -W "accepted running" -- "${cur##*=}" ) )
|
||||
return
|
||||
;;
|
||||
name)
|
||||
__docker_complete_services --cur "${cur##*=}" --name
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
COMPREPLY=( $( compgen -W "desired-state id label name" -S = -- "$cur" ) )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --no-resolve --filter --all" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_nodes_plus_self
|
||||
local counter=$(__docker_pos_first_nonflag '--filter|-f')
|
||||
if [ $cword -eq $counter ]; then
|
||||
__docker_complete_nodes_plus_self
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_update() {
|
||||
case "$prev" in
|
||||
--availability)
|
||||
COMPREPLY=( $( compgen -W "active drain pause" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--membership)
|
||||
COMPREPLY=( $( compgen -W "accepted rejected" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--role)
|
||||
COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --availability --membership --role" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--availability --help --membership --role" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_nodes
|
||||
@@ -1964,7 +2272,7 @@ _docker_run() {
|
||||
--cpu-quota
|
||||
--cpuset-cpus
|
||||
--cpuset-mems
|
||||
--cpu-shares
|
||||
--cpu-shares -c
|
||||
--device
|
||||
--device-read-bps
|
||||
--device-read-iops
|
||||
@@ -1987,6 +2295,7 @@ _docker_run() {
|
||||
--label-file
|
||||
--label -l
|
||||
--link
|
||||
--link-local-ip
|
||||
--log-driver
|
||||
--log-opt
|
||||
--mac-address
|
||||
@@ -2006,6 +2315,7 @@ _docker_run() {
|
||||
--security-opt
|
||||
--shm-size
|
||||
--stop-signal
|
||||
--storage-opt
|
||||
--tmpfs
|
||||
--sysctl
|
||||
--ulimit
|
||||
@@ -2192,6 +2502,11 @@ _docker_run() {
|
||||
fi
|
||||
return
|
||||
;;
|
||||
--storage-opt)
|
||||
COMPREPLY=( $( compgen -W "size" -S = -- "$cur") )
|
||||
__docker_nospace
|
||||
return
|
||||
;;
|
||||
--user|-u)
|
||||
__docker_complete_user_group
|
||||
return
|
||||
@@ -2359,7 +2674,7 @@ _docker_update() {
|
||||
--cpu-quota
|
||||
--cpuset-cpus
|
||||
--cpuset-mems
|
||||
--cpu-shares
|
||||
--cpu-shares -c
|
||||
--kernel-memory
|
||||
--memory -m
|
||||
--memory-reservation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#compdef docker
|
||||
#compdef docker dockerd
|
||||
#
|
||||
# zsh completion for docker (http://docker.com)
|
||||
#
|
||||
@@ -299,6 +299,17 @@ __docker_complete_pid() {
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_complete_runtimes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
||||
emulate -L zsh
|
||||
setopt extendedglob
|
||||
local -a runtimes_opts
|
||||
runtimes_opts=(${(ps: :)${(f)${${"$(_call_program commands docker $docker_options info)"##*$'\n'Runtimes: }%%$'\n'^ *}}})
|
||||
_describe -t runtimes-opts "runtimes options" runtimes_opts && ret=0
|
||||
}
|
||||
|
||||
__docker_complete_ps_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
@@ -415,8 +426,8 @@ __docker_complete_events_filter() {
|
||||
(event)
|
||||
local -a event_opts
|
||||
event_opts=('attach' 'commit' 'connect' 'copy' 'create' 'delete' 'destroy' 'detach' 'die' 'disconnect' 'exec_create' 'exec_detach'
|
||||
'exec_start' 'export' 'import' 'kill' 'mount' 'oom' 'pause' 'pull' 'push' 'reload' 'rename' 'resize' 'restart' 'start' 'stop' 'tag'
|
||||
'top' 'unmount' 'unpause' 'untag' 'update')
|
||||
'exec_start' 'export' 'import' 'kill' 'load' 'mount' 'oom' 'pause' 'pull' 'push' 'reload' 'rename' 'resize' 'restart' 'save' 'start'
|
||||
'stop' 'tag' 'top' 'unmount' 'unpause' 'untag' 'update')
|
||||
_describe -t event-filter-opts "event filter options" event_opts && ret=0
|
||||
;;
|
||||
(image)
|
||||
@@ -538,7 +549,7 @@ __docker_networks_names() {
|
||||
__docker_network_commands() {
|
||||
local -a _docker_network_subcommands
|
||||
_docker_network_subcommands=(
|
||||
"connect:Connects a container to a network"
|
||||
"connect:Connect a container to a network"
|
||||
"create:Creates a new network with a name specified by the user"
|
||||
"disconnect:Disconnects a container from a network"
|
||||
"inspect:Displays detailed information on a network"
|
||||
@@ -563,6 +574,7 @@ __docker_network_subcommand() {
|
||||
"($help)--ip=[Container IPv4 address]:IPv4: " \
|
||||
"($help)--ip6=[Container IPv6 address]:IPv6: " \
|
||||
"($help)*--link=[Add a link to another container]:link:->link" \
|
||||
"($help)*--link-local-ip=[Add a link-local address for the container]:IPv4/IPv6: " \
|
||||
"($help -)1:network:__docker_networks" \
|
||||
"($help -)2:containers:__docker_containers" && ret=0
|
||||
|
||||
@@ -629,6 +641,602 @@ __docker_network_subcommand() {
|
||||
return ret
|
||||
}
|
||||
|
||||
# BO node
|
||||
|
||||
__docker_node_complete_ls_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
(id)
|
||||
__docker_complete_nodes_ids && ret=0
|
||||
;;
|
||||
(membership)
|
||||
membership_opts=('accepted' 'pending' 'rejected')
|
||||
_describe -t membership-opts "membership options" membership_opts && ret=0
|
||||
;;
|
||||
(name)
|
||||
__docker_complete_nodes_names && ret=0
|
||||
;;
|
||||
(role)
|
||||
role_opts=('manager' 'worker')
|
||||
_describe -t role-opts "role options" role_opts && ret=0
|
||||
;;
|
||||
*)
|
||||
_message 'value' && ret=0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
opts=('id' 'label' 'membership' 'name' 'role')
|
||||
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_node_complete_tasks_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
(desired-state)
|
||||
state_opts=('accepted' 'running')
|
||||
_describe -t state-opts "desired state options" state_opts && ret=0
|
||||
;;
|
||||
*)
|
||||
_message 'value' && ret=0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
opts=('desired-state' 'id' 'label' 'name')
|
||||
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_nodes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local line s
|
||||
declare -a lines nodes args
|
||||
|
||||
type=$1; shift
|
||||
filter=$1; shift
|
||||
[[ $filter != "none" ]] && args=("-f $filter")
|
||||
|
||||
lines=(${(f)"$(_call_program commands docker $docker_options node ls $args)"})
|
||||
|
||||
# Parse header line to find columns
|
||||
local i=1 j=1 k header=${lines[1]}
|
||||
declare -A begin end
|
||||
while (( j < ${#header} - 1 )); do
|
||||
i=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 1 ))
|
||||
j=$(( i + ${${header[$i,-1]}[(i) ]} - 1 ))
|
||||
k=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 2 ))
|
||||
begin[${header[$i,$((j-1))]}]=$i
|
||||
end[${header[$i,$((j-1))]}]=$k
|
||||
done
|
||||
end[${header[$i,$((j-1))]}]=-1
|
||||
lines=(${lines[2,-1]})
|
||||
|
||||
# Node ID
|
||||
if [[ $type = (ids|all) ]]; then
|
||||
for line in $lines; do
|
||||
s="${line[${begin[ID]},${end[ID]}]%% ##}"
|
||||
nodes=($nodes $s)
|
||||
done
|
||||
fi
|
||||
|
||||
# Names
|
||||
if [[ $type = (names|all) ]]; then
|
||||
for line in $lines; do
|
||||
s="${line[${begin[NAME]},${end[NAME]}]%% ##}"
|
||||
nodes=($nodes $s)
|
||||
done
|
||||
fi
|
||||
|
||||
_describe -t nodes-list "nodes" nodes "$@" && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_complete_nodes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes all none "$@"
|
||||
}
|
||||
|
||||
__docker_complete_nodes_ids() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes ids none "$@"
|
||||
}
|
||||
|
||||
__docker_complete_nodes_names() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes names none "$@"
|
||||
}
|
||||
|
||||
__docker_complete_pending_nodes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes all "membership=pending" "$@"
|
||||
}
|
||||
|
||||
__docker_complete_manager_nodes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes all "role=manager" "$@"
|
||||
}
|
||||
|
||||
__docker_complete_worker_nodes() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_nodes all "role=worker" "$@"
|
||||
}
|
||||
|
||||
__docker_node_commands() {
|
||||
local -a _docker_node_subcommands
|
||||
_docker_node_subcommands=(
|
||||
"accept:Accept a node in the swarm"
|
||||
"demote:Demote a node as manager in the swarm"
|
||||
"inspect:Display detailed information on one or more nodes"
|
||||
"ls:List nodes in the swarm"
|
||||
"promote:Promote a node as manager in the swarm"
|
||||
"rm:Remove a node from the swarm"
|
||||
"tasks:List tasks running on a node"
|
||||
"update:Update a node"
|
||||
)
|
||||
_describe -t docker-node-commands "docker node command" _docker_node_subcommands
|
||||
}
|
||||
|
||||
__docker_node_subcommand() {
|
||||
local -a _command_args opts_help
|
||||
local expl help="--help"
|
||||
integer ret=1
|
||||
|
||||
opts_help=("(: -)--help[Print usage]")
|
||||
|
||||
case "$words[1]" in
|
||||
(accept|rm|remove)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)*:node:__docker_complete_pending_nodes" && ret=0
|
||||
;;
|
||||
(demote)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)*:node:__docker_complete_manager_nodes" && ret=0
|
||||
;;
|
||||
(inspect)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
|
||||
"($help -p --pretty)"{-p,--pretty}"[Print the information in a human friendly format]" \
|
||||
"($help -)*:node:__docker_complete_nodes" && ret=0
|
||||
;;
|
||||
(ls|list)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
|
||||
"($help -q --quiet)"{-q,--quiet}"[Only display IDs]" && ret=0
|
||||
case $state in
|
||||
(filter-options)
|
||||
__docker_node_complete_ls_filters && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(promote)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)*:node:__docker_complete_worker_nodes" && ret=0
|
||||
;;
|
||||
(tasks)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -a --all)"{-a,--all}"[Display all instances]" \
|
||||
"($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
|
||||
"($help -n --no-resolve)"{-n,--no-resolve}"[Do not map IDs to Names]" \
|
||||
"($help -)1:node:__docker_complete_nodes" && ret=0
|
||||
case $state in
|
||||
(filter-options)
|
||||
__docker_node_complete_tasks_filters && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(update)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)--availability=[Availability of the node]:availability:(active pause drain)" \
|
||||
"($help)--membership=[Membership of the node]:membership:(accepted rejected)" \
|
||||
"($help)--role=[Role of the node]:role:(manager worker)" \
|
||||
"($help -)1:node:__docker_complete_nodes" && ret=0
|
||||
;;
|
||||
(help)
|
||||
_arguments $(__docker_arguments) ":subcommand:__docker_node_commands" && ret=0
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
# EO node
|
||||
|
||||
# BO plugin
|
||||
|
||||
__docker_complete_plugins() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local line s
|
||||
declare -a lines plugins
|
||||
|
||||
lines=(${(f)"$(_call_program commands docker $docker_options plugin ls)"})
|
||||
|
||||
# Parse header line to find columns
|
||||
local i=1 j=1 k header=${lines[1]}
|
||||
declare -A begin end
|
||||
while (( j < ${#header} - 1 )); do
|
||||
i=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 1 ))
|
||||
j=$(( i + ${${header[$i,-1]}[(i) ]} - 1 ))
|
||||
k=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 2 ))
|
||||
begin[${header[$i,$((j-1))]}]=$i
|
||||
end[${header[$i,$((j-1))]}]=$k
|
||||
done
|
||||
end[${header[$i,$((j-1))]}]=-1
|
||||
lines=(${lines[2,-1]})
|
||||
|
||||
# Name
|
||||
for line in $lines; do
|
||||
s="${line[${begin[NAME]},${end[NAME]}]%% ##}"
|
||||
s="$s:${(l:7:: :::)${${line[${begin[TAG]},${end[TAG]}]}%% ##}}"
|
||||
plugins=($plugins $s)
|
||||
done
|
||||
|
||||
_describe -t plugins-list "plugins" plugins "$@" && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_plugin_commands() {
|
||||
local -a _docker_plugin_subcommands
|
||||
_docker_plugin_subcommands=(
|
||||
"disable:Disable a plugin"
|
||||
"enable:Enable a plugin"
|
||||
"inspect:Return low-level information about a plugin"
|
||||
"install:Install a plugin"
|
||||
"ls:List plugins"
|
||||
"push:Push a plugin"
|
||||
"rm:Remove a plugin"
|
||||
"set:Change settings for a plugin"
|
||||
)
|
||||
_describe -t docker-plugin-commands "docker plugin command" _docker_plugin_subcommands
|
||||
}
|
||||
|
||||
__docker_plugin_subcommand() {
|
||||
local -a _command_args opts_help
|
||||
local expl help="--help"
|
||||
integer ret=1
|
||||
|
||||
opts_help=("(: -)--help[Print usage]")
|
||||
|
||||
case "$words[1]" in
|
||||
(disable|enable|inspect|install|ls|push|rm)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)1:plugin:__docker_complete_plugins" && ret=0
|
||||
;;
|
||||
(set)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)1:plugin:__docker_complete_plugins" \
|
||||
"($help-)*:key=value: " && ret=0
|
||||
;;
|
||||
(help)
|
||||
_arguments $(__docker_arguments) ":subcommand:__docker_plugin_commands" && ret=0
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
# EO plugin
|
||||
|
||||
# BO service
|
||||
|
||||
__docker_service_complete_ls_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
(id)
|
||||
__docker_complete_services_ids && ret=0
|
||||
;;
|
||||
(name)
|
||||
__docker_complete_services_names && ret=0
|
||||
;;
|
||||
*)
|
||||
_message 'value' && ret=0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
opts=('id' 'label' 'name')
|
||||
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_service_complete_tasks_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
|
||||
if compset -P '*='; then
|
||||
case "${${words[-1]%=*}#*=}" in
|
||||
(desired-state)
|
||||
state_opts=('accepted' 'running')
|
||||
_describe -t state-opts "desired state options" state_opts && ret=0
|
||||
;;
|
||||
*)
|
||||
_message 'value' && ret=0
|
||||
;;
|
||||
esac
|
||||
else
|
||||
opts=('desired-state' 'id' 'label' 'name')
|
||||
_describe -t filter-opts "filter options" opts -qS "=" && ret=0
|
||||
fi
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_services() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
local line s
|
||||
declare -a lines services
|
||||
|
||||
type=$1; shift
|
||||
|
||||
lines=(${(f)"$(_call_program commands docker $docker_options service ls)"})
|
||||
|
||||
# Parse header line to find columns
|
||||
local i=1 j=1 k header=${lines[1]}
|
||||
declare -A begin end
|
||||
while (( j < ${#header} - 1 )); do
|
||||
i=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 1 ))
|
||||
j=$(( i + ${${header[$i,-1]}[(i) ]} - 1 ))
|
||||
k=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 2 ))
|
||||
begin[${header[$i,$((j-1))]}]=$i
|
||||
end[${header[$i,$((j-1))]}]=$k
|
||||
done
|
||||
end[${header[$i,$((j-1))]}]=-1
|
||||
lines=(${lines[2,-1]})
|
||||
|
||||
# Service ID
|
||||
if [[ $type = (ids|all) ]]; then
|
||||
for line in $lines; do
|
||||
s="${line[${begin[ID]},${end[ID]}]%% ##}"
|
||||
s="$s:${(l:7:: :::)${${line[${begin[IMAGE]},${end[IMAGE]}]}%% ##}}"
|
||||
services=($services $s)
|
||||
done
|
||||
fi
|
||||
|
||||
# Names
|
||||
if [[ $type = (names|all) ]]; then
|
||||
for line in $lines; do
|
||||
s="${line[${begin[NAME]},${end[NAME]}]%% ##}"
|
||||
s="$s:${(l:7:: :::)${${line[${begin[IMAGE]},${end[IMAGE]}]}%% ##}}"
|
||||
services=($services $s)
|
||||
done
|
||||
fi
|
||||
|
||||
_describe -t services-list "services" services "$@" && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
__docker_complete_services() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_services all "$@"
|
||||
}
|
||||
|
||||
__docker_complete_services_ids() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_services ids "$@"
|
||||
}
|
||||
|
||||
__docker_complete_services_names() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
__docker_services names "$@"
|
||||
}
|
||||
|
||||
__docker_service_commands() {
|
||||
local -a _docker_service_subcommands
|
||||
_docker_service_subcommands=(
|
||||
"create:Create a new service"
|
||||
"inspect:Display detailed information on one or more services"
|
||||
"ls:List services"
|
||||
"rm:Remove a service"
|
||||
"scale:Scale one or multiple services"
|
||||
"tasks:List the tasks of a service"
|
||||
"update:Update a service"
|
||||
)
|
||||
_describe -t docker-service-commands "docker service command" _docker_service_subcommands
|
||||
}
|
||||
|
||||
__docker_service_subcommand() {
|
||||
local -a _command_args opts_help opts_create_update
|
||||
local expl help="--help"
|
||||
integer ret=1
|
||||
|
||||
opts_help=("(: -)--help[Print usage]")
|
||||
opts_create_update=(
|
||||
"($help)*--constraint=[Placement constraints]:constraint: "
|
||||
"($help)--endpoint-mode=[Placement constraints]:mode:(VIP DNSRR)"
|
||||
"($help)*"{-e=,--env=}"[Set environment variables]:env: "
|
||||
"($help)*--label=[Service labels]:label: "
|
||||
"($help)--limit-cpu=[Limit CPUs]:value: "
|
||||
"($help)--limit-memory=[Limit Memory]:value: "
|
||||
"($help)--mode=[Limit Memory]:mode:(global replicated)"
|
||||
"($help)*"{-m=,--mount=}"[Attach a mount to the service]:mount: "
|
||||
"($help)--name=[Service name]:name: "
|
||||
"($help)*--network=[Network attachments]:network: "
|
||||
"($help)*"{-p=,--publish=}"[Publish a port as a node port]:port: "
|
||||
"($help)--replicas=[Number of tasks]:replicas: "
|
||||
"($help)--reserve-cpu=[Reserve CPUs]:value: "
|
||||
"($help)--reserve-memory=[Reserve Memory]:value: "
|
||||
"($help)--restart-condition=[Restart when condition is met]:mode:(any none on-failure)"
|
||||
"($help)--restart-delay=[Delay between restart attempts]:delay: "
|
||||
"($help)--restart-max-attempts=[Maximum number of restarts before giving up]:max-attempts: "
|
||||
"($help)--restart-window=[Window used to evaluate the restart policy]:window: "
|
||||
"($help)--stop-grace-period=[Time to wait before force killing a container]:grace period: "
|
||||
"($help)--update-delay=[Delay between updates]:delay: "
|
||||
"($help)--update-parallelism=[Maximum number of tasks updated simultaneously]:number: "
|
||||
"($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users"
|
||||
"($help -w --workdir)"{-w=,--workdir=}"[Working directory inside the container]:directory:_directories"
|
||||
)
|
||||
|
||||
case "$words[1]" in
|
||||
(create)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
$opts_create_update \
|
||||
"($help -): :__docker_images" \
|
||||
"($help -):command: _command_names -e" \
|
||||
"($help -)*::arguments: _normal" && ret=0
|
||||
;;
|
||||
(inspect)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " \
|
||||
"($help -p --pretty)"{-p,--pretty}"[Print the information in a human friendly format]" \
|
||||
"($help -)*:service:__docker_complete_services" && ret=0
|
||||
;;
|
||||
(ls|list)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)*"{-f=,--filter=}"[Filter output based on conditions provided]:filter:->filter-options" \
|
||||
"($help -q --quiet)"{-q,--quiet}"[Only display IDs]" && ret=0
|
||||
case $state in
|
||||
(filter-options)
|
||||
__docker_service_complete_ls_filters && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(rm|remove)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)*:service:__docker_complete_services" && ret=0
|
||||
;;
|
||||
(scale)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -)*:service:->values" && ret=0
|
||||
case $state in
|
||||
(values)
|
||||
if compset -P '*='; then
|
||||
_message 'replicas' && ret=0
|
||||
else
|
||||
__docker_complete_services -qS "="
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(tasks)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -a --all)"{-a,--all}"[Display all tasks]" \
|
||||
"($help)*"{-f=,--filter=}"[Provide filter values]:filter:->filter-options" \
|
||||
"($help -n --no-resolve)"{-n,--no-resolve}"[Do not map IDs to Names]" \
|
||||
"($help -)1:service:__docker_complete_services" && ret=0
|
||||
case $state in
|
||||
(filter-options)
|
||||
__docker_service_complete_tasks_filters && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(update)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
$opts_create_update \
|
||||
"($help)--arg=[Service command args]:arguments: _normal" \
|
||||
"($help)--command=[Service command]:command: _command_names -e" \
|
||||
"($help)--image=[Service image tag]:image:__docker_repositories" \
|
||||
"($help -)1:service:__docker_complete_services" && ret=0
|
||||
;;
|
||||
(help)
|
||||
_arguments $(__docker_arguments) ":subcommand:__docker_service_commands" && ret=0
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
# EO service
|
||||
|
||||
# BO swarm
|
||||
|
||||
__docker_swarm_commands() {
|
||||
local -a _docker_swarm_subcommands
|
||||
_docker_swarm_subcommands=(
|
||||
"init:Initialize a Swarm"
|
||||
"inspect:Inspect the Swarm"
|
||||
"join:Join a Swarm as a node and/or manager"
|
||||
"leave:Leave a Swarm"
|
||||
"update:Update the Swarm"
|
||||
)
|
||||
_describe -t docker-swarm-commands "docker swarm command" _docker_swarm_subcommands
|
||||
}
|
||||
|
||||
__docker_swarm_subcommand() {
|
||||
local -a _command_args opts_help
|
||||
local expl help="--help"
|
||||
integer ret=1
|
||||
|
||||
opts_help=("(: -)--help[Print usage]")
|
||||
|
||||
case "$words[1]" in
|
||||
(init)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)--auto-accept=[Acceptance policy]:policy:(manager none worker)" \
|
||||
"($help)*--external-ca=[Specifications of one or more certificate signing endpoints]:endpoint: " \
|
||||
"($help)--force-new-cluster[Force create a new cluster from current state]" \
|
||||
"($help)--listen-addr[Listen address]:ip\:port: " \
|
||||
"($help)--secret[Set secret value needed to accept nodes into cluster]:secret: " && ret=0
|
||||
;;
|
||||
(inspect)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -f --format)"{-f=,--format=}"[Format the output using the given go template]:template: " && ret=0
|
||||
;;
|
||||
(join)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)--ca-hash=[Hash of the Root Certificate Authority certificate used for trusted join]:hash: " \
|
||||
"($help)--listen-addr[Listen address]:ip\:port: " \
|
||||
"($help)--manager[Try joining as a manager]" \
|
||||
"($help)--secret[Secret for node acceptance]:secret: " \
|
||||
"($help -):host\:port: " && ret=0
|
||||
;;
|
||||
(leave)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help && ret=0
|
||||
;;
|
||||
(update)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)--auto-accept=[Acceptance policy]:policy:(manager none worker)" \
|
||||
"($help)--cert-expiry=[Validity period for node certificates]:duration: " \
|
||||
"($help)--dispatcher-heartbeat=[Dispatcher heartbeat period]:duration: " \
|
||||
"($help)--secret[Set secret value needed to accept nodes into cluster]:secret: " \
|
||||
"($help)--task-history-limit[Task history retention limit]:limit: " && ret=0
|
||||
;;
|
||||
(help)
|
||||
_arguments $(__docker_arguments) ":subcommand:__docker_network_commands" && ret=0
|
||||
;;
|
||||
esac
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
# EO swarm
|
||||
|
||||
__docker_volume_complete_ls_filters() {
|
||||
[[ $PREFIX = -* ]] && return 1
|
||||
integer ret=1
|
||||
@@ -693,7 +1301,7 @@ __docker_volume_commands() {
|
||||
local -a _docker_volume_subcommands
|
||||
_docker_volume_subcommands=(
|
||||
"create:Create a volume"
|
||||
"inspect:Return low-level information on a volume"
|
||||
"inspect:Display detailed information on one or more volumes"
|
||||
"ls:List volumes"
|
||||
"rm:Remove a volume"
|
||||
)
|
||||
@@ -786,7 +1394,7 @@ __docker_subcommand() {
|
||||
"($help)--userns=[Container user namespace]:user namespace:(host)"
|
||||
)
|
||||
opts_build_create_run_update=(
|
||||
"($help)--cpu-shares=[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)"
|
||||
"($help -c --cpu-shares)"{-c=,--cpu-shares=}"[CPU shares (relative weight)]:CPU shares:(0 10 100 200 500 800 1000)"
|
||||
"($help)--cpu-period=[Limit the CPU CFS (Completely Fair Scheduler) period]:CPU period: "
|
||||
"($help)--cpu-quota=[Limit the CPU CFS (Completely Fair Scheduler) quota]:CPU quota: "
|
||||
"($help)--cpuset-cpus=[CPUs in which to allow execution]:CPUs: "
|
||||
@@ -820,6 +1428,7 @@ __docker_subcommand() {
|
||||
"($help)--ip6=[Container IPv6 address]:IPv6: "
|
||||
"($help)--ipc=[IPC namespace to use]:IPC namespace: "
|
||||
"($help)*--link=[Add link to another container]:link:->link"
|
||||
"($help)*--link-local-ip=[Add a link-local address for the container]:IPv4/IPv6: "
|
||||
"($help)*"{-l=,--label=}"[Container metadata]:label: "
|
||||
"($help)--log-driver=[Default driver for container logs]:Logging driver:(awslogs etwlogs fluentd gcplogs gelf journald json-file none splunk syslog)"
|
||||
"($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options"
|
||||
@@ -931,6 +1540,7 @@ __docker_subcommand() {
|
||||
(daemon)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help)*--add-runtime=[Register an additional OCI compatible runtime]:runtime:__docker_complete_runtimes" \
|
||||
"($help)--api-cors-header=[CORS headers in the remote API]:CORS headers: " \
|
||||
"($help)*--authorization-plugin=[Authorization plugins to load]" \
|
||||
"($help -b --bridge)"{-b=,--bridge=}"[Attach containers to a network bridge]:bridge:_net_interfaces" \
|
||||
@@ -965,6 +1575,7 @@ __docker_subcommand() {
|
||||
"($help)--ipv6[Enable IPv6 networking]" \
|
||||
"($help -l --log-level)"{-l=,--log-level=}"[Logging level]:level:(debug info warn error fatal)" \
|
||||
"($help)*--label=[Key=value labels]:label: " \
|
||||
"($help)--live-restore[Enable live restore of docker when containers are still running]" \
|
||||
"($help)--log-driver=[Default driver for container logs]:Logging driver:(awslogs etwlogs fluentd gcplogs gelf journald json-file none splunk syslog)" \
|
||||
"($help)*--log-opt=[Log driver specific options]:log driver options:__docker_log_options" \
|
||||
"($help)--max-concurrent-downloads[Set the max concurrent downloads for each pull]" \
|
||||
@@ -973,7 +1584,7 @@ __docker_subcommand() {
|
||||
"($help -p --pidfile)"{-p=,--pidfile=}"[Path to use for daemon PID file]:PID file:_files" \
|
||||
"($help)--raw-logs[Full timestamps without ANSI coloring]" \
|
||||
"($help)*--registry-mirror=[Preferred Docker registry mirror]:registry mirror: " \
|
||||
"($help -s --storage-driver)"{-s=,--storage-driver=}"[Storage driver to use]:driver:(aufs devicemapper btrfs zfs overlay)" \
|
||||
"($help -s --storage-driver)"{-s=,--storage-driver=}"[Storage driver to use]:driver:(aufs btrfs devicemapper overlay overlay2 vfs zfs)" \
|
||||
"($help)--selinux-enabled[Enable selinux support]" \
|
||||
"($help)*--storage-opt=[Storage driver options]:storage driver options: " \
|
||||
"($help)--tls[Use TLS]" \
|
||||
@@ -1159,6 +1770,23 @@ __docker_subcommand() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(node)
|
||||
local curcontext="$curcontext" state
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -): :->command" \
|
||||
"($help -)*:: :->option-or-argument" && ret=0
|
||||
|
||||
case $state in
|
||||
(command)
|
||||
__docker_node_commands && ret=0
|
||||
;;
|
||||
(option-or-argument)
|
||||
curcontext=${curcontext%:*:*}:docker-${words[-1]}:
|
||||
__docker_node_subcommand && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(pause|unpause)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
@@ -1248,8 +1876,10 @@ __docker_subcommand() {
|
||||
"($help)--health-timeout=[Maximum time to allow one check to run]:time: " \
|
||||
"($help)--no-healthcheck[Disable any container-specified HEALTHCHECK]" \
|
||||
"($help)--rm[Remove intermediate containers when it exits]" \
|
||||
"($help)--runtime=[Name of the runtime to be used for that container]:runtime:__docker_complete_runtimes" \
|
||||
"($help)--sig-proxy[Proxy all received signals to the process (non-TTY mode only)]" \
|
||||
"($help)--stop-signal=[Signal to kill a container]:signal:_signals" \
|
||||
"($help)--storage-opt=[Set storage driver options per container]:storage options:->storage-opt" \
|
||||
"($help -): :__docker_images" \
|
||||
"($help -):command: _command_names -e" \
|
||||
"($help -)*::arguments: _normal" && ret=0
|
||||
@@ -1262,6 +1892,14 @@ __docker_subcommand() {
|
||||
__docker_runningcontainers -qS ":" && ret=0
|
||||
fi
|
||||
;;
|
||||
(storage-opt)
|
||||
if compset -P "*="; then
|
||||
_message "value" && ret=0
|
||||
else
|
||||
opts=('size')
|
||||
_describe -t filter-opts "storage options" opts -qS "=" && ret=0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
;;
|
||||
@@ -1285,6 +1923,23 @@ __docker_subcommand() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(service)
|
||||
local curcontext="$curcontext" state
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -): :->command" \
|
||||
"($help -)*:: :->option-or-argument" && ret=0
|
||||
|
||||
case $state in
|
||||
(command)
|
||||
__docker_service_commands && ret=0
|
||||
;;
|
||||
(option-or-argument)
|
||||
curcontext=${curcontext%:*:*}:docker-${words[-1]}:
|
||||
__docker_service_subcommand && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(start)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
@@ -1300,6 +1955,23 @@ __docker_subcommand() {
|
||||
"($help)--no-stream[Disable streaming stats and only pull the first result]" \
|
||||
"($help -)*:containers:__docker_runningcontainers" && ret=0
|
||||
;;
|
||||
(swarm)
|
||||
local curcontext="$curcontext" state
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
"($help -): :->command" \
|
||||
"($help -)*:: :->option-or-argument" && ret=0
|
||||
|
||||
case $state in
|
||||
(command)
|
||||
__docker_swarm_commands && ret=0
|
||||
;;
|
||||
(option-or-argument)
|
||||
curcontext=${curcontext%:*:*}:docker-${words[-1]}:
|
||||
__docker_swarm_subcommand && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(tag)
|
||||
_arguments $(__docker_arguments) \
|
||||
$opts_help \
|
||||
@@ -1410,6 +2082,13 @@ _docker() {
|
||||
return ret
|
||||
}
|
||||
|
||||
_dockerd() {
|
||||
integer ret=1
|
||||
words[1]='daemon'
|
||||
__docker_subcommand && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
_docker "$@"
|
||||
|
||||
# Local Variables:
|
||||
|
||||
@@ -11,8 +11,10 @@ Type=notify
|
||||
# for containers run by docker
|
||||
ExecStart=/usr/bin/dockerd -H fd://
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
# Having non-zero Limit*s causes performance problems due to accounting overhead
|
||||
# in the kernel. We recommend using cgroups to do container-local accounting.
|
||||
LimitNOFILE=infinity
|
||||
LimitNPROC=infinity
|
||||
LimitCORE=infinity
|
||||
# Uncomment TasksMax if your systemd version supports it.
|
||||
# Only systemd 226 and above support this version.
|
||||
@@ -20,6 +22,8 @@ LimitCORE=infinity
|
||||
TimeoutStartSec=0
|
||||
# set delegate yes so that systemd does not reset the cgroups of docker containers
|
||||
Delegate=yes
|
||||
# kill only the docker process, not all processes in the cgroup
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
||||
Target: m.Target,
|
||||
Source: m.Source,
|
||||
Type: types.MountType(strings.ToLower(swarmapi.Mount_MountType_name[int32(m.Type)])),
|
||||
Writable: m.Writable,
|
||||
ReadOnly: m.ReadOnly,
|
||||
}
|
||||
|
||||
if m.BindOptions != nil {
|
||||
@@ -37,8 +37,8 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
|
||||
|
||||
if m.VolumeOptions != nil {
|
||||
mount.VolumeOptions = &types.VolumeOptions{
|
||||
Populate: m.VolumeOptions.Populate,
|
||||
Labels: m.VolumeOptions.Labels,
|
||||
NoCopy: m.VolumeOptions.NoCopy,
|
||||
Labels: m.VolumeOptions.Labels,
|
||||
}
|
||||
if m.VolumeOptions.DriverConfig != nil {
|
||||
mount.VolumeOptions.DriverConfig = &types.Driver{
|
||||
@@ -77,7 +77,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||
mount := swarmapi.Mount{
|
||||
Target: m.Target,
|
||||
Source: m.Source,
|
||||
Writable: m.Writable,
|
||||
ReadOnly: m.ReadOnly,
|
||||
}
|
||||
|
||||
if mountType, ok := swarmapi.Mount_MountType_value[strings.ToUpper(string(m.Type))]; ok {
|
||||
@@ -98,8 +98,8 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||
|
||||
if m.VolumeOptions != nil {
|
||||
mount.VolumeOptions = &swarmapi.Mount_VolumeOptions{
|
||||
Populate: m.VolumeOptions.Populate,
|
||||
Labels: m.VolumeOptions.Labels,
|
||||
NoCopy: m.VolumeOptions.NoCopy,
|
||||
Labels: m.VolumeOptions.Labels,
|
||||
}
|
||||
if m.VolumeOptions.DriverConfig != nil {
|
||||
mount.VolumeOptions.DriverConfig = &swarmapi.Driver{
|
||||
|
||||
@@ -148,17 +148,22 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
|
||||
}
|
||||
}
|
||||
|
||||
return basictypes.NetworkResource{
|
||||
nr := basictypes.NetworkResource{
|
||||
ID: n.ID,
|
||||
Name: n.Spec.Annotations.Name,
|
||||
Scope: "swarm",
|
||||
Driver: n.DriverState.Name,
|
||||
EnableIPv6: spec.Ipv6Enabled,
|
||||
IPAM: ipam,
|
||||
Internal: spec.Internal,
|
||||
Options: n.DriverState.Options,
|
||||
Labels: n.Spec.Annotations.Labels,
|
||||
}
|
||||
|
||||
if n.DriverState != nil {
|
||||
nr.Driver = n.DriverState.Name
|
||||
nr.Options = n.DriverState.Options
|
||||
}
|
||||
|
||||
return nr
|
||||
}
|
||||
|
||||
// BasicNetworkCreateToGRPC converts a NetworkCreateRequest to a grpc NetworkSpec.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user