mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Compare commits
238 Commits
v19.03.3-b
...
v1.12.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
129
CHANGELOG.md
129
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))
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -164,7 +164,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 +180,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 +191,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"
|
||||
]`)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -49,7 +49,7 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
||||
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 {
|
||||
if !opts.all && !filter.Include("desired_state") {
|
||||
filter.Add("desired_state", string(swarm.TaskStateRunning))
|
||||
filter.Add("desired_state", string(swarm.TaskStateAccepted))
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -24,20 +24,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()
|
||||
headers := map[string][]string{}
|
||||
|
||||
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
|
||||
}
|
||||
headers["X-Registry-Auth"] = []string{encodedAuth}
|
||||
}
|
||||
|
||||
response, err := apiClient.ServiceCreate(ctx, service, headers)
|
||||
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, " Writable = %v\n", v.Writable)
|
||||
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 {
|
||||
@@ -177,7 +184,7 @@ func (m *MountOpt) Set(value string) error {
|
||||
}
|
||||
|
||||
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]
|
||||
@@ -191,14 +198,14 @@ func (m *MountOpt) Set(value string) error {
|
||||
case "writable":
|
||||
mount.Writable, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invald value for writable: %s", err.Error())
|
||||
return fmt.Errorf("invalid value for writable: %s", value)
|
||||
}
|
||||
case "bind-propagation":
|
||||
mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
||||
bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
||||
case "volume-populate":
|
||||
volumeOptions().Populate, 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 +217,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +242,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 +317,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 +373,8 @@ type serviceOptions struct {
|
||||
update updateOptions
|
||||
networks []string
|
||||
endpoint endpointOptions
|
||||
|
||||
registryAuth bool
|
||||
}
|
||||
|
||||
func newServiceOptions() *serviceOptions {
|
||||
@@ -428,7 +438,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 +447,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.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(Valid values: vip, 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"
|
||||
)
|
||||
|
||||
115
api/client/service/opts_test.go
Normal file
115
api/client/service/opts_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
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,writable=yes"), "invalid value for writable: yes")
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
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 +61,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 +77,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, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ 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
|
||||
}
|
||||
|
||||
@@ -18,18 +18,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 +37,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()
|
||||
headers := map[string][]string{}
|
||||
|
||||
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
|
||||
}
|
||||
headers["X-Registry-Auth"] = []string{encodedAuth}
|
||||
}
|
||||
|
||||
err = apiClient.ServiceUpdate(ctx, service.ID, service.Version, service.Spec, headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -59,52 +76,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 +129,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 +170,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 +202,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 +248,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"})
|
||||
}
|
||||
@@ -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 headers with X-Registry-Auth
|
||||
if err := apiClient.ServiceUpdate(
|
||||
ctx,
|
||||
service.ID,
|
||||
service.Version,
|
||||
serviceSpec,
|
||||
nil,
|
||||
); 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, nil); 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,12 @@ 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
|
||||
}
|
||||
|
||||
bundle, err := bundlefile.LoadFile(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading %s: %v\n", path, err)
|
||||
}
|
||||
|
||||
@@ -9,49 +9,49 @@ import (
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
@@ -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,10 +1,15 @@
|
||||
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 (
|
||||
@@ -13,6 +18,14 @@ const (
|
||||
WORKER = "WORKER"
|
||||
// MANAGER constant for manager name
|
||||
MANAGER = "MANAGER"
|
||||
|
||||
flagAutoAccept = "auto-accept"
|
||||
flagCertExpiry = "cert-expiry"
|
||||
flagDispatcherHeartbeat = "dispatcher-heartbeat"
|
||||
flagListenAddr = "listen-addr"
|
||||
flagSecret = "secret"
|
||||
flagTaskHistoryLimit = "task-history-limit"
|
||||
flagExternalCA = "external-ca"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,6 +35,15 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
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,9 +69,19 @@ 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
|
||||
@@ -65,10 +92,10 @@ type AutoAcceptOption 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)
|
||||
for key, value := range o.values {
|
||||
keys = append(keys, fmt.Sprintf("%s=%v", strings.ToLower(key), value))
|
||||
}
|
||||
return strings.Join(keys, " ")
|
||||
return strings.Join(keys, ", ")
|
||||
}
|
||||
|
||||
// Set sets a new value on this option
|
||||
@@ -102,7 +129,7 @@ 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 {
|
||||
@@ -118,3 +145,115 @@ func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
|
||||
func NewAutoAcceptOption() AutoAcceptOption {
|
||||
return AutoAcceptOption{values: make(map[string]bool)}
|
||||
}
|
||||
|
||||
// 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 accept nodes into 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
|
||||
}
|
||||
|
||||
120
api/client/swarm/opts_test.go
Normal file
120
api/client/swarm/opts_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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.values[WORKER], true)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetManager(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("manager"))
|
||||
assert.Equal(t, opt.values[MANAGER], true)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetInvalid(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.Error(t, opt.Set("bogus"), "must be one of")
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetNone(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("none"))
|
||||
assert.Equal(t, opt.values[MANAGER], false)
|
||||
assert.Equal(t, opt.values[WORKER], false)
|
||||
}
|
||||
|
||||
func TestAutoAcceptOptionSetConflict(t *testing.T) {
|
||||
opt := NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("manager"))
|
||||
assert.Error(t, opt.Set("none"), "value NONE is incompatible with MANAGER")
|
||||
|
||||
opt = NewAutoAcceptOption()
|
||||
assert.NilError(t, opt.Set("none"))
|
||||
assert.Error(t, opt.Set("worker"), "value NONE is incompatible with WORKER")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -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,6 +41,7 @@ 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
|
||||
@@ -66,31 +54,41 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOpt
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
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, ", ")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
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:'
|
||||
|
||||
@@ -693,7 +693,7 @@ _docker_build() {
|
||||
--cgroup-parent
|
||||
--cpuset-cpus
|
||||
--cpuset-mems
|
||||
--cpu-shares
|
||||
--cpu-shares -c
|
||||
--cpu-period
|
||||
--cpu-quota
|
||||
--file -f
|
||||
@@ -942,10 +942,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 +966,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 +1053,7 @@ _docker_events() {
|
||||
export
|
||||
import
|
||||
kill
|
||||
load
|
||||
mount
|
||||
oom
|
||||
pause
|
||||
@@ -1058,6 +1063,7 @@ _docker_events() {
|
||||
rename
|
||||
resize
|
||||
restart
|
||||
save
|
||||
start
|
||||
stop
|
||||
tag
|
||||
@@ -1355,6 +1361,7 @@ _docker_network_connect() {
|
||||
--ip
|
||||
--ip6
|
||||
--link
|
||||
--link-local-ip
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
@@ -1528,11 +1535,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 +1555,47 @@ _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() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --all --filter --no-resolve" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "-f --filter --help --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_remove() {
|
||||
_docker_service_rm
|
||||
}
|
||||
|
||||
_docker_service_rm() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
@@ -1594,21 +1606,131 @@ _docker_service_rm() {
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_ls() {
|
||||
_docker_service_scale() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
COMPREPLY=( $(compgen -S "=" -W "$(__docker_services $1)" -- "$cur") )
|
||||
__docker_nospace
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_service_tasks() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve -n" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_services
|
||||
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,14 +1745,44 @@ _docker_swarm() {
|
||||
}
|
||||
|
||||
_docker_swarm_init() {
|
||||
case "$prev" in
|
||||
--auto-accept)
|
||||
COMPREPLY=( $( compgen -W "manager none worker" -- "$cur" ) )
|
||||
return
|
||||
;;
|
||||
--listen-addr|--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|--listen-addr|--secret)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--ca-hash --help --listen-addr --manager --secret" -- "$cur" ) )
|
||||
@@ -1638,14 +1790,6 @@ _docker_swarm_join() {
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_update() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_swarm_leave() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
@@ -1654,10 +1798,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 +1821,9 @@ _docker_node() {
|
||||
accept
|
||||
demote
|
||||
inspect
|
||||
ls
|
||||
ls list
|
||||
promote
|
||||
rm
|
||||
rm remove
|
||||
tasks
|
||||
update
|
||||
"
|
||||
@@ -1695,20 +1849,46 @@ _docker_node_accept() {
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_inspect() {
|
||||
_docker_node_demote() {
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --format --pretty" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_manager_nodes
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_inspect() {
|
||||
case "$prev" in
|
||||
--format|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--format -f --help --pretty -p" -- "$cur" ) )
|
||||
;;
|
||||
*)
|
||||
__docker_complete_nodes
|
||||
esac
|
||||
}
|
||||
|
||||
_docker_node_list() {
|
||||
_docker_node_ls
|
||||
}
|
||||
|
||||
_docker_node_ls() {
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--help --filter --quiet" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--filter -f --help --quiet -q" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -1723,14 +1903,8 @@ _docker_node_promote() {
|
||||
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,9 +1918,15 @@ _docker_node_rm() {
|
||||
}
|
||||
|
||||
_docker_node_tasks() {
|
||||
case "$prev" in
|
||||
--filter|-f)
|
||||
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
|
||||
@@ -1754,9 +1934,24 @@ _docker_node_tasks() {
|
||||
}
|
||||
|
||||
_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 +2159,7 @@ _docker_run() {
|
||||
--cpu-quota
|
||||
--cpuset-cpus
|
||||
--cpuset-mems
|
||||
--cpu-shares
|
||||
--cpu-shares -c
|
||||
--device
|
||||
--device-read-bps
|
||||
--device-read-iops
|
||||
@@ -1987,6 +2182,7 @@ _docker_run() {
|
||||
--label-file
|
||||
--label -l
|
||||
--link
|
||||
--link-local-ip
|
||||
--log-driver
|
||||
--log-opt
|
||||
--mac-address
|
||||
@@ -2006,6 +2202,7 @@ _docker_run() {
|
||||
--security-opt
|
||||
--shm-size
|
||||
--stop-signal
|
||||
--storage-opt
|
||||
--tmpfs
|
||||
--sysctl
|
||||
--ulimit
|
||||
@@ -2192,6 +2389,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 +2561,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)
|
||||
#
|
||||
@@ -415,8 +415,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 +538,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 +563,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
|
||||
|
||||
@@ -693,7 +694,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 +787,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 +821,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"
|
||||
@@ -965,6 +967,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 +976,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]" \
|
||||
@@ -1250,6 +1253,7 @@ __docker_subcommand() {
|
||||
"($help)--rm[Remove intermediate containers when it exits]" \
|
||||
"($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 +1266,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
|
||||
|
||||
;;
|
||||
@@ -1410,6 +1422,13 @@ _docker() {
|
||||
return ret
|
||||
}
|
||||
|
||||
_dockerd() {
|
||||
integer ret=1
|
||||
words[1]='daemon'
|
||||
__docker_subcommand && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
_docker "$@"
|
||||
|
||||
# Local Variables:
|
||||
|
||||
@@ -20,6 +20,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
|
||||
|
||||
@@ -13,10 +13,12 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/daemon/cluster/convert"
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
"github.com/docker/docker/daemon/cluster/executor/container"
|
||||
"github.com/docker/docker/errors"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/runconfig"
|
||||
apitypes "github.com/docker/engine-api/types"
|
||||
@@ -28,25 +30,46 @@ import (
|
||||
|
||||
const swarmDirName = "swarm"
|
||||
const controlSocket = "control.sock"
|
||||
const swarmConnectTimeout = 10 * time.Second
|
||||
const swarmConnectTimeout = 20 * time.Second
|
||||
const stateFile = "docker-state.json"
|
||||
const defaultAddr = "0.0.0.0:2377"
|
||||
|
||||
const (
|
||||
initialReconnectDelay = 100 * time.Millisecond
|
||||
maxReconnectDelay = 10 * time.Second
|
||||
maxReconnectDelay = 30 * time.Second
|
||||
)
|
||||
|
||||
// ErrNoManager is returned then a manager-only function is called on non-manager
|
||||
var ErrNoManager = fmt.Errorf("this node is not participating as a Swarm manager")
|
||||
|
||||
// ErrNoSwarm is returned on leaving a cluster that was never initialized
|
||||
var ErrNoSwarm = fmt.Errorf("this node is not part of Swarm")
|
||||
var ErrNoSwarm = fmt.Errorf("This node is not part of Swarm")
|
||||
|
||||
// ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated
|
||||
var ErrSwarmExists = fmt.Errorf("this node is already part of a Swarm")
|
||||
var ErrSwarmExists = fmt.Errorf("This node is already part of a Swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
|
||||
|
||||
// ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
|
||||
var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
|
||||
|
||||
// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
|
||||
var ErrSwarmJoinTimeoutReached = fmt.Errorf("timeout reached before node was joined")
|
||||
var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.")
|
||||
|
||||
// defaultSpec contains some sane defaults if cluster options are missing on init
|
||||
var defaultSpec = types.Spec{
|
||||
Raft: types.RaftConfig{
|
||||
SnapshotInterval: 10000,
|
||||
KeepOldSnapshots: 0,
|
||||
LogEntriesForSlowFollowers: 500,
|
||||
HeartbeatTick: 1,
|
||||
ElectionTick: 3,
|
||||
},
|
||||
CAConfig: types.CAConfig{
|
||||
NodeCertExpiry: 90 * 24 * time.Hour,
|
||||
},
|
||||
Dispatcher: types.DispatcherConfig{
|
||||
HeartbeatPeriod: uint64((5 * time.Second).Nanoseconds()),
|
||||
},
|
||||
Orchestration: types.OrchestrationConfig{
|
||||
TaskHistoryRetentionLimit: 10,
|
||||
},
|
||||
}
|
||||
|
||||
type state struct {
|
||||
ListenAddr string
|
||||
@@ -59,22 +82,27 @@ type Config struct {
|
||||
Backend executorpkg.Backend
|
||||
}
|
||||
|
||||
// Cluster provides capabilities to pariticipate in a cluster as worker or a
|
||||
// manager and a worker.
|
||||
// Cluster provides capabilities to participate in a cluster as a worker or a
|
||||
// manager.
|
||||
type Cluster struct {
|
||||
sync.RWMutex
|
||||
root string
|
||||
config Config
|
||||
configEvent chan struct{} // todo: make this array and goroutine safe
|
||||
node *swarmagent.Node
|
||||
*node
|
||||
root string
|
||||
config Config
|
||||
configEvent chan struct{} // todo: make this array and goroutine safe
|
||||
listenAddr string
|
||||
stop bool
|
||||
err error
|
||||
cancelDelay func()
|
||||
}
|
||||
|
||||
type node struct {
|
||||
*swarmagent.Node
|
||||
done chan struct{}
|
||||
ready bool
|
||||
conn *grpc.ClientConn
|
||||
client swarmapi.ControlClient
|
||||
ready bool
|
||||
listenAddr string
|
||||
err error
|
||||
reconnectDelay time.Duration
|
||||
stop bool
|
||||
cancelDelay func()
|
||||
}
|
||||
|
||||
// New creates a new Cluster instance using provided config.
|
||||
@@ -84,13 +112,12 @@ func New(config Config) (*Cluster, error) {
|
||||
return nil, err
|
||||
}
|
||||
c := &Cluster{
|
||||
root: root,
|
||||
config: config,
|
||||
configEvent: make(chan struct{}, 10),
|
||||
reconnectDelay: initialReconnectDelay,
|
||||
root: root,
|
||||
config: config,
|
||||
configEvent: make(chan struct{}, 10),
|
||||
}
|
||||
|
||||
dt, err := ioutil.ReadFile(filepath.Join(root, stateFile))
|
||||
st, err := c.loadState()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return c, nil
|
||||
@@ -98,12 +125,7 @@ func New(config Config) (*Cluster, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var st state
|
||||
if err := json.Unmarshal(dt, &st); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
n, ctx, err := c.startNewNode(false, st.ListenAddr, "", "", "", false)
|
||||
n, err := c.startNewNode(false, st.ListenAddr, "", "", "", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -111,16 +133,33 @@ func New(config Config) (*Cluster, error) {
|
||||
select {
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
logrus.Errorf("swarm component could not be started before timeout was reached")
|
||||
case <-n.Ready(context.Background()):
|
||||
case <-ctx.Done():
|
||||
case <-n.Ready():
|
||||
case <-n.done:
|
||||
return nil, fmt.Errorf("swarm component could not be started: %v", c.err)
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("swarm component could not be started")
|
||||
}
|
||||
go c.reconnectOnFailure(ctx)
|
||||
go c.reconnectOnFailure(n)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) loadState() (*state, error) {
|
||||
dt, err := ioutil.ReadFile(filepath.Join(c.root, stateFile))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// missing certificate means no actual state to restore from
|
||||
if _, err := os.Stat(filepath.Join(c.root, "certificates/swarm-node.crt")); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.clearState()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var st state
|
||||
if err := json.Unmarshal(dt, &st); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &st, nil
|
||||
}
|
||||
|
||||
func (c *Cluster) saveState() error {
|
||||
dt, err := json.Marshal(state{ListenAddr: c.listenAddr})
|
||||
if err != nil {
|
||||
@@ -129,20 +168,20 @@ func (c *Cluster) saveState() error {
|
||||
return ioutils.AtomicWriteFile(filepath.Join(c.root, stateFile), dt, 0600)
|
||||
}
|
||||
|
||||
func (c *Cluster) reconnectOnFailure(ctx context.Context) {
|
||||
func (c *Cluster) reconnectOnFailure(n *node) {
|
||||
for {
|
||||
<-ctx.Done()
|
||||
<-n.done
|
||||
c.Lock()
|
||||
if c.stop || c.node != nil {
|
||||
c.Unlock()
|
||||
return
|
||||
}
|
||||
c.reconnectDelay *= 2
|
||||
if c.reconnectDelay > maxReconnectDelay {
|
||||
c.reconnectDelay = maxReconnectDelay
|
||||
n.reconnectDelay *= 2
|
||||
if n.reconnectDelay > maxReconnectDelay {
|
||||
n.reconnectDelay = maxReconnectDelay
|
||||
}
|
||||
logrus.Warnf("Restarting swarm in %.2f seconds", c.reconnectDelay.Seconds())
|
||||
delayCtx, cancel := context.WithTimeout(context.Background(), c.reconnectDelay)
|
||||
logrus.Warnf("Restarting swarm in %.2f seconds", n.reconnectDelay.Seconds())
|
||||
delayCtx, cancel := context.WithTimeout(context.Background(), n.reconnectDelay)
|
||||
c.cancelDelay = cancel
|
||||
c.Unlock()
|
||||
<-delayCtx.Done()
|
||||
@@ -155,22 +194,23 @@ func (c *Cluster) reconnectOnFailure(ctx context.Context) {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
_, ctx, err = c.startNewNode(false, c.listenAddr, c.getRemoteAddress(), "", "", false)
|
||||
n, err = c.startNewNode(false, c.listenAddr, c.getRemoteAddress(), "", "", false)
|
||||
if err != nil {
|
||||
c.err = err
|
||||
ctx = delayCtx
|
||||
close(n.done)
|
||||
}
|
||||
c.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secret, cahash string, ismanager bool) (*swarmagent.Node, context.Context, error) {
|
||||
func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secret, cahash string, ismanager bool) (*node, error) {
|
||||
if err := c.config.Backend.IsSwarmCompatible(); err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
c.node = nil
|
||||
c.cancelDelay = nil
|
||||
node, err := swarmagent.NewNode(&swarmagent.NodeConfig{
|
||||
c.stop = false
|
||||
n, err := swarmagent.NewNode(&swarmagent.NodeConfig{
|
||||
Hostname: c.config.Name,
|
||||
ForceNewCluster: forceNewCluster,
|
||||
ListenControlAPI: filepath.Join(c.root, controlSocket),
|
||||
@@ -185,87 +225,85 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre
|
||||
IsManager: ismanager,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
if err := node.Start(ctx); err != nil {
|
||||
return nil, nil, err
|
||||
ctx := context.Background()
|
||||
if err := n.Start(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node := &node{
|
||||
Node: n,
|
||||
done: make(chan struct{}),
|
||||
reconnectDelay: initialReconnectDelay,
|
||||
}
|
||||
|
||||
c.node = node
|
||||
c.listenAddr = listenAddr
|
||||
c.saveState()
|
||||
c.config.Backend.SetClusterProvider(c)
|
||||
go func() {
|
||||
err := node.Err(ctx)
|
||||
err := n.Err(ctx)
|
||||
if err != nil {
|
||||
logrus.Errorf("cluster exited with error: %v", err)
|
||||
}
|
||||
c.Lock()
|
||||
c.conn = nil
|
||||
c.client = nil
|
||||
c.node = nil
|
||||
c.ready = false
|
||||
c.err = err
|
||||
c.Unlock()
|
||||
cancel()
|
||||
close(node.done)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-node.Ready(context.Background()):
|
||||
case <-n.Ready():
|
||||
c.Lock()
|
||||
c.reconnectDelay = initialReconnectDelay
|
||||
c.Unlock()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
if ctx.Err() == nil {
|
||||
c.Lock()
|
||||
c.ready = true
|
||||
node.ready = true
|
||||
c.err = nil
|
||||
c.Unlock()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
c.configEvent <- struct{}{}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for conn := range node.ListenControlSocket(ctx) {
|
||||
for conn := range n.ListenControlSocket(ctx) {
|
||||
c.Lock()
|
||||
if c.conn != conn {
|
||||
c.client = swarmapi.NewControlClient(conn)
|
||||
if node.conn != conn {
|
||||
if conn == nil {
|
||||
node.client = nil
|
||||
} else {
|
||||
node.client = swarmapi.NewControlClient(conn)
|
||||
}
|
||||
}
|
||||
if c.conn != nil {
|
||||
c.client = nil
|
||||
}
|
||||
c.conn = conn
|
||||
node.conn = conn
|
||||
c.Unlock()
|
||||
c.configEvent <- struct{}{}
|
||||
}
|
||||
}()
|
||||
|
||||
return node, ctx, nil
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// Init initializes new cluster from user provided request.
|
||||
func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
||||
c.Lock()
|
||||
if c.node != nil {
|
||||
c.Unlock()
|
||||
if node := c.node; node != nil {
|
||||
if !req.ForceNewCluster {
|
||||
return "", ErrSwarmExists
|
||||
c.Unlock()
|
||||
return "", errSwarmExists(node)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
if err := c.node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
if err := c.stopNode(); err != nil {
|
||||
c.Unlock()
|
||||
return "", err
|
||||
}
|
||||
c.Lock()
|
||||
c.node = nil
|
||||
c.conn = nil
|
||||
c.ready = false
|
||||
}
|
||||
|
||||
if err := validateAndSanitizeInitRequest(&req); err != nil {
|
||||
c.Unlock()
|
||||
return "", err
|
||||
}
|
||||
|
||||
// todo: check current state existing
|
||||
n, ctx, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false)
|
||||
n, err := c.startNewNode(req.ForceNewCluster, req.ListenAddr, "", "", "", false)
|
||||
if err != nil {
|
||||
c.Unlock()
|
||||
return "", err
|
||||
@@ -273,71 +311,88 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
||||
c.Unlock()
|
||||
|
||||
select {
|
||||
case <-n.Ready(context.Background()):
|
||||
if err := initAcceptancePolicy(n, req.Spec.AcceptancePolicy); err != nil {
|
||||
case <-n.Ready():
|
||||
if err := initClusterSpec(n, req.Spec); err != nil {
|
||||
return "", err
|
||||
}
|
||||
go c.reconnectOnFailure(ctx)
|
||||
go c.reconnectOnFailure(n)
|
||||
return n.NodeID(), nil
|
||||
case <-ctx.Done():
|
||||
case <-n.done:
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.err != nil {
|
||||
if !req.ForceNewCluster { // if failure on first attempt don't keep state
|
||||
if err := c.clearState(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !req.ForceNewCluster { // if failure on first attempt don't keep state
|
||||
if err := c.clearState(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", c.err
|
||||
}
|
||||
return "", ctx.Err()
|
||||
return "", c.err
|
||||
}
|
||||
}
|
||||
|
||||
// Join makes current Cluster part of an existing swarm cluster.
|
||||
func (c *Cluster) Join(req types.JoinRequest) error {
|
||||
c.Lock()
|
||||
if c.node != nil {
|
||||
if node := c.node; node != nil {
|
||||
c.Unlock()
|
||||
return ErrSwarmExists
|
||||
return errSwarmExists(node)
|
||||
}
|
||||
if err := validateAndSanitizeJoinRequest(&req); err != nil {
|
||||
c.Unlock()
|
||||
return err
|
||||
}
|
||||
// todo: check current state existing
|
||||
if len(req.RemoteAddrs) == 0 {
|
||||
return fmt.Errorf("at least 1 RemoteAddr is required to join")
|
||||
}
|
||||
n, ctx, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager)
|
||||
n, err := c.startNewNode(false, req.ListenAddr, req.RemoteAddrs[0], req.Secret, req.CACertHash, req.Manager)
|
||||
if err != nil {
|
||||
c.Unlock()
|
||||
return err
|
||||
}
|
||||
c.Unlock()
|
||||
|
||||
select {
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
go c.reconnectOnFailure(ctx)
|
||||
if nodeid := n.NodeID(); nodeid != "" {
|
||||
return fmt.Errorf("Timeout reached before node was joined. Your cluster settings may be preventing this node from automatically joining. To accept this node into cluster run `docker node accept %v` in an existing cluster manager", nodeid)
|
||||
}
|
||||
return ErrSwarmJoinTimeoutReached
|
||||
case <-n.Ready(context.Background()):
|
||||
go c.reconnectOnFailure(ctx)
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.err != nil {
|
||||
certificateRequested := n.CertificateRequested()
|
||||
for {
|
||||
select {
|
||||
case <-certificateRequested:
|
||||
if n.NodeMembership() == swarmapi.NodeMembershipPending {
|
||||
return fmt.Errorf("Your node is in the process of joining the cluster but needs to be accepted by existing cluster member.\nTo accept this node into cluster run \"docker node accept %v\" in an existing cluster manager. Use \"docker info\" command to see the current Swarm status of your node.", n.NodeID())
|
||||
}
|
||||
certificateRequested = nil
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
// attempt to connect will continue in background, also reconnecting
|
||||
go c.reconnectOnFailure(n)
|
||||
return ErrSwarmJoinTimeoutReached
|
||||
case <-n.Ready():
|
||||
go c.reconnectOnFailure(n)
|
||||
return nil
|
||||
case <-n.done:
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return c.err
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cluster) cancelReconnect() {
|
||||
// stopNode is a helper that stops the active c.node and waits until it has
|
||||
// shut down. Call while keeping the cluster lock.
|
||||
func (c *Cluster) stopNode() error {
|
||||
if c.node == nil {
|
||||
return nil
|
||||
}
|
||||
c.stop = true
|
||||
if c.cancelDelay != nil {
|
||||
c.cancelDelay()
|
||||
c.cancelDelay = nil
|
||||
}
|
||||
node := c.node
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
// TODO: can't hold lock on stop because it calls back to network
|
||||
c.Unlock()
|
||||
defer c.Lock()
|
||||
if err := node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
return err
|
||||
}
|
||||
<-node.done
|
||||
return nil
|
||||
}
|
||||
|
||||
// Leave shuts down Cluster and removes current state.
|
||||
@@ -360,36 +415,29 @@ func (c *Cluster) Leave(force bool) error {
|
||||
c.Unlock()
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
msg += fmt.Sprintf("Leaving cluster will leave you with %v managers out of %v. This means Raft quorum will be lost and your cluster will become inaccessible. ", reachable-1, reachable+unreachable)
|
||||
msg += fmt.Sprintf("Leaving cluster will leave you with %v managers out of %v. This means Raft quorum will be lost and your cluster will become inaccessible. ", reachable-1, reachable+unreachable)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg += "Doing so may lose the consenus of your cluster. "
|
||||
msg += "Doing so may lose the consensus of your cluster. "
|
||||
}
|
||||
|
||||
msg += "Only way to restore a cluster that has lost consensus is to reinitialize it with `--force-new-cluster`. Use `--force` to ignore this message."
|
||||
c.Unlock()
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
c.cancelReconnect()
|
||||
c.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
if err := node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
if err := c.stopNode(); err != nil {
|
||||
c.Unlock()
|
||||
return err
|
||||
}
|
||||
nodeID := node.NodeID()
|
||||
for _, id := range c.config.Backend.ListContainersForNode(nodeID) {
|
||||
if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
logrus.Errorf("error removing %v: %v", id, err)
|
||||
c.Unlock()
|
||||
if nodeID := node.NodeID(); nodeID != "" {
|
||||
for _, id := range c.config.Backend.ListContainersForNode(nodeID) {
|
||||
if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
logrus.Errorf("error removing %v: %v", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.node = nil
|
||||
c.conn = nil
|
||||
c.ready = false
|
||||
c.configEvent <- struct{}{}
|
||||
// todo: cleanup optional?
|
||||
if err := c.clearState(); err != nil {
|
||||
@@ -399,6 +447,7 @@ func (c *Cluster) Leave(force bool) error {
|
||||
}
|
||||
|
||||
func (c *Cluster) clearState() error {
|
||||
// todo: backup this data instead of removing?
|
||||
if err := os.RemoveAll(c.root); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -414,13 +463,13 @@ func (c *Cluster) getRequestContext() context.Context { // TODO: not needed when
|
||||
return ctx
|
||||
}
|
||||
|
||||
// Inspect retrives the confuguration properties of managed swarm cluster.
|
||||
// Inspect retrieves the configuration properties of a managed swarm cluster.
|
||||
func (c *Cluster) Inspect() (types.Swarm, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return types.Swarm{}, ErrNoManager
|
||||
return types.Swarm{}, c.errNoManager()
|
||||
}
|
||||
|
||||
swarm, err := getSwarm(c.getRequestContext(), c.client)
|
||||
@@ -441,15 +490,15 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
swarmSpec, err := convert.SwarmSpecToGRPC(spec)
|
||||
swarm, err := getSwarm(c.getRequestContext(), c.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
swarm, err := getSwarm(c.getRequestContext(), c.client)
|
||||
swarmSpec, err := convert.SwarmSpecToGRPCandMerge(spec, &swarm.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -467,32 +516,32 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsManager returns true is Cluster is participating as a manager.
|
||||
// IsManager returns true if Cluster is participating as a manager.
|
||||
func (c *Cluster) IsManager() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return c.isActiveManager()
|
||||
}
|
||||
|
||||
// IsAgent returns true is Cluster is participating as a worker/agent.
|
||||
// IsAgent returns true if Cluster is participating as a worker/agent.
|
||||
func (c *Cluster) IsAgent() bool {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return c.ready
|
||||
return c.node != nil && c.ready
|
||||
}
|
||||
|
||||
// GetListenAddress returns the listening address for current maanger's
|
||||
// GetListenAddress returns the listening address for current manager's
|
||||
// consensus and dispatcher APIs.
|
||||
func (c *Cluster) GetListenAddress() string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.conn != nil {
|
||||
if c.isActiveManager() {
|
||||
return c.listenAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetRemoteAddress returns a known advertise address of a remote maanger if
|
||||
// GetRemoteAddress returns a known advertise address of a remote manager if
|
||||
// available.
|
||||
// todo: change to array/connect with info
|
||||
func (c *Cluster) GetRemoteAddress() string {
|
||||
@@ -541,7 +590,6 @@ func (c *Cluster) Info() types.Info {
|
||||
if c.err != nil {
|
||||
info.Error = c.err.Error()
|
||||
}
|
||||
|
||||
if c.isActiveManager() {
|
||||
info.ControlAvailable = true
|
||||
if r, err := c.client.ListNodes(c.getRequestContext(), &swarmapi.ListNodesRequest{}); err == nil {
|
||||
@@ -570,7 +618,19 @@ func (c *Cluster) Info() types.Info {
|
||||
|
||||
// isActiveManager should not be called without a read lock
|
||||
func (c *Cluster) isActiveManager() bool {
|
||||
return c.conn != nil
|
||||
return c.node != nil && c.conn != nil
|
||||
}
|
||||
|
||||
// errNoManager returns error describing why manager commands can't be used.
|
||||
// Call with read lock.
|
||||
func (c *Cluster) errNoManager() error {
|
||||
if c.node == nil {
|
||||
return fmt.Errorf("This node is not a Swarm manager. Use \"docker swarm init\" or \"docker swarm join --manager\" to connect this node to Swarm and try again.")
|
||||
}
|
||||
if c.node.Manager() != nil {
|
||||
return fmt.Errorf("This node is not a Swarm manager. Manager is being prepared or has trouble connecting to the cluster.")
|
||||
}
|
||||
return fmt.Errorf("This node is not a Swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.")
|
||||
}
|
||||
|
||||
// GetServices returns all services of a managed swarm cluster.
|
||||
@@ -579,7 +639,7 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return nil, ErrNoManager
|
||||
return nil, c.errNoManager()
|
||||
}
|
||||
|
||||
filters, err := newListServicesFilters(options.Filter)
|
||||
@@ -603,12 +663,12 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
|
||||
}
|
||||
|
||||
// CreateService creates a new service in a managed swarm cluster.
|
||||
func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
|
||||
func (c *Cluster) CreateService(s types.ServiceSpec, encodedAuth string) (string, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return "", ErrNoManager
|
||||
return "", c.errNoManager()
|
||||
}
|
||||
|
||||
ctx := c.getRequestContext()
|
||||
@@ -622,6 +682,15 @@ func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if encodedAuth != "" {
|
||||
ctnr := serviceSpec.Task.GetContainer()
|
||||
if ctnr == nil {
|
||||
return "", fmt.Errorf("service does not use container tasks")
|
||||
}
|
||||
ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
|
||||
}
|
||||
|
||||
r, err := c.client.CreateService(ctx, &swarmapi.CreateServiceRequest{Spec: &serviceSpec})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -630,13 +699,13 @@ func (c *Cluster) CreateService(s types.ServiceSpec) (string, error) {
|
||||
return r.Service.ID, nil
|
||||
}
|
||||
|
||||
// GetService returns a service based on a ID or name.
|
||||
// GetService returns a service based on an ID or name.
|
||||
func (c *Cluster) GetService(input string) (types.Service, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return types.Service{}, ErrNoManager
|
||||
return types.Service{}, c.errNoManager()
|
||||
}
|
||||
|
||||
service, err := getService(c.getRequestContext(), c.client, input)
|
||||
@@ -647,12 +716,12 @@ func (c *Cluster) GetService(input string) (types.Service, error) {
|
||||
}
|
||||
|
||||
// UpdateService updates existing service to match new properties.
|
||||
func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec) error {
|
||||
func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.ServiceSpec, encodedAuth string) error {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
serviceSpec, err := convert.ServiceSpecToGRPC(spec)
|
||||
@@ -660,6 +729,26 @@ func (c *Cluster) UpdateService(serviceID string, version uint64, spec types.Ser
|
||||
return err
|
||||
}
|
||||
|
||||
if encodedAuth != "" {
|
||||
ctnr := serviceSpec.Task.GetContainer()
|
||||
if ctnr == nil {
|
||||
return fmt.Errorf("service does not use container tasks")
|
||||
}
|
||||
ctnr.PullOptions = &swarmapi.ContainerSpec_PullOptions{RegistryAuth: encodedAuth}
|
||||
} else {
|
||||
// this is needed because if the encodedAuth isn't being updated then we
|
||||
// shouldn't lose it, and continue to use the one that was already present
|
||||
currentService, err := getService(c.getRequestContext(), c.client, serviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctnr := currentService.Spec.Task.GetContainer()
|
||||
if ctnr == nil {
|
||||
return fmt.Errorf("service does not use container tasks")
|
||||
}
|
||||
serviceSpec.Task.GetContainer().PullOptions = ctnr.PullOptions
|
||||
}
|
||||
|
||||
_, err = c.client.UpdateService(
|
||||
c.getRequestContext(),
|
||||
&swarmapi.UpdateServiceRequest{
|
||||
@@ -679,7 +768,7 @@ func (c *Cluster) RemoveService(input string) error {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
service, err := getService(c.getRequestContext(), c.client, input)
|
||||
@@ -699,7 +788,7 @@ func (c *Cluster) GetNodes(options apitypes.NodeListOptions) ([]types.Node, erro
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return nil, ErrNoManager
|
||||
return nil, c.errNoManager()
|
||||
}
|
||||
|
||||
filters, err := newListNodesFilters(options.Filter)
|
||||
@@ -721,13 +810,13 @@ func (c *Cluster) GetNodes(options apitypes.NodeListOptions) ([]types.Node, erro
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// GetNode returns a node based on a ID or name.
|
||||
// GetNode returns a node based on an ID or name.
|
||||
func (c *Cluster) GetNode(input string) (types.Node, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return types.Node{}, ErrNoManager
|
||||
return types.Node{}, c.errNoManager()
|
||||
}
|
||||
|
||||
node, err := getNode(c.getRequestContext(), c.client, input)
|
||||
@@ -743,7 +832,7 @@ func (c *Cluster) UpdateNode(nodeID string, version uint64, spec types.NodeSpec)
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
nodeSpec, err := convert.NodeSpecToGRPC(spec)
|
||||
@@ -770,7 +859,7 @@ func (c *Cluster) RemoveNode(input string) error {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
ctx := c.getRequestContext()
|
||||
@@ -792,7 +881,7 @@ func (c *Cluster) GetTasks(options apitypes.TaskListOptions) ([]types.Task, erro
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return nil, ErrNoManager
|
||||
return nil, c.errNoManager()
|
||||
}
|
||||
|
||||
filters, err := newListTasksFilters(options.Filter)
|
||||
@@ -820,7 +909,7 @@ func (c *Cluster) GetTask(input string) (types.Task, error) {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return types.Task{}, ErrNoManager
|
||||
return types.Task{}, c.errNoManager()
|
||||
}
|
||||
|
||||
task, err := getTask(c.getRequestContext(), c.client, input)
|
||||
@@ -830,13 +919,13 @@ func (c *Cluster) GetTask(input string) (types.Task, error) {
|
||||
return convert.TaskFromGRPC(*task), nil
|
||||
}
|
||||
|
||||
// GetNetwork returns a cluster network by ID.
|
||||
// GetNetwork returns a cluster network by an ID.
|
||||
func (c *Cluster) GetNetwork(input string) (apitypes.NetworkResource, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return apitypes.NetworkResource{}, ErrNoManager
|
||||
return apitypes.NetworkResource{}, c.errNoManager()
|
||||
}
|
||||
|
||||
network, err := getNetwork(c.getRequestContext(), c.client, input)
|
||||
@@ -852,7 +941,7 @@ func (c *Cluster) GetNetworks() ([]apitypes.NetworkResource, error) {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return nil, ErrNoManager
|
||||
return nil, c.errNoManager()
|
||||
}
|
||||
|
||||
r, err := c.client.ListNetworks(c.getRequestContext(), &swarmapi.ListNetworksRequest{})
|
||||
@@ -875,7 +964,7 @@ func (c *Cluster) CreateNetwork(s apitypes.NetworkCreateRequest) (string, error)
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return "", ErrNoManager
|
||||
return "", c.errNoManager()
|
||||
}
|
||||
|
||||
if runconfig.IsPreDefinedNetwork(s.Name) {
|
||||
@@ -898,7 +987,7 @@ func (c *Cluster) RemoveNetwork(input string) error {
|
||||
defer c.RUnlock()
|
||||
|
||||
if !c.isActiveManager() {
|
||||
return ErrNoManager
|
||||
return c.errNoManager()
|
||||
}
|
||||
|
||||
network, err := getNetwork(c.getRequestContext(), c.client, input)
|
||||
@@ -918,7 +1007,7 @@ func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.S
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Networks[i] = types.NetworkAttachmentConfig{Target: apiNetwork.ID}
|
||||
s.Networks[i].Target = apiNetwork.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -958,7 +1047,7 @@ func (c *Cluster) Cleanup() {
|
||||
c.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
defer c.Unlock()
|
||||
if c.isActiveManager() {
|
||||
active, reachable, unreachable, err := c.managerStats()
|
||||
if err == nil {
|
||||
@@ -968,18 +1057,7 @@ func (c *Cluster) Cleanup() {
|
||||
}
|
||||
}
|
||||
}
|
||||
c.cancelReconnect()
|
||||
c.Unlock()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
if err := node.Stop(ctx); err != nil {
|
||||
logrus.Errorf("error cleaning up cluster: %v", err)
|
||||
}
|
||||
c.Lock()
|
||||
c.node = nil
|
||||
c.ready = false
|
||||
c.conn = nil
|
||||
c.Unlock()
|
||||
c.stopNode()
|
||||
}
|
||||
|
||||
func (c *Cluster) managerStats() (current bool, reachable int, unreachable int, err error) {
|
||||
@@ -1004,7 +1082,84 @@ func (c *Cluster) managerStats() (current bool, reachable int, unreachable int,
|
||||
return
|
||||
}
|
||||
|
||||
func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.AcceptancePolicy) error {
|
||||
func validateAndSanitizeInitRequest(req *types.InitRequest) error {
|
||||
var err error
|
||||
req.ListenAddr, err = validateAddr(req.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
|
||||
}
|
||||
|
||||
spec := &req.Spec
|
||||
// provide sane defaults instead of erroring
|
||||
if spec.Name == "" {
|
||||
spec.Name = "default"
|
||||
}
|
||||
if spec.Raft.SnapshotInterval == 0 {
|
||||
spec.Raft.SnapshotInterval = defaultSpec.Raft.SnapshotInterval
|
||||
}
|
||||
if spec.Raft.LogEntriesForSlowFollowers == 0 {
|
||||
spec.Raft.LogEntriesForSlowFollowers = defaultSpec.Raft.LogEntriesForSlowFollowers
|
||||
}
|
||||
if spec.Raft.ElectionTick == 0 {
|
||||
spec.Raft.ElectionTick = defaultSpec.Raft.ElectionTick
|
||||
}
|
||||
if spec.Raft.HeartbeatTick == 0 {
|
||||
spec.Raft.HeartbeatTick = defaultSpec.Raft.HeartbeatTick
|
||||
}
|
||||
if spec.Dispatcher.HeartbeatPeriod == 0 {
|
||||
spec.Dispatcher.HeartbeatPeriod = defaultSpec.Dispatcher.HeartbeatPeriod
|
||||
}
|
||||
if spec.CAConfig.NodeCertExpiry == 0 {
|
||||
spec.CAConfig.NodeCertExpiry = defaultSpec.CAConfig.NodeCertExpiry
|
||||
}
|
||||
if spec.Orchestration.TaskHistoryRetentionLimit == 0 {
|
||||
spec.Orchestration.TaskHistoryRetentionLimit = defaultSpec.Orchestration.TaskHistoryRetentionLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAndSanitizeJoinRequest(req *types.JoinRequest) error {
|
||||
var err error
|
||||
req.ListenAddr, err = validateAddr(req.ListenAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid ListenAddr %q: %v", req.ListenAddr, err)
|
||||
}
|
||||
if len(req.RemoteAddrs) == 0 {
|
||||
return fmt.Errorf("at least 1 RemoteAddr is required to join")
|
||||
}
|
||||
for i := range req.RemoteAddrs {
|
||||
req.RemoteAddrs[i], err = validateAddr(req.RemoteAddrs[i])
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid remoteAddr %q: %v", req.RemoteAddrs[i], err)
|
||||
}
|
||||
}
|
||||
if req.CACertHash != "" {
|
||||
if _, err := digest.ParseDigest(req.CACertHash); err != nil {
|
||||
return fmt.Errorf("invalid CACertHash %q, %v", req.CACertHash, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAddr(addr string) (string, error) {
|
||||
if addr == "" {
|
||||
return addr, fmt.Errorf("invalid empty address")
|
||||
}
|
||||
newaddr, err := opts.ParseTCPAddr(addr, defaultAddr)
|
||||
if err != nil {
|
||||
return addr, nil
|
||||
}
|
||||
return strings.TrimPrefix(newaddr, "tcp://"), nil
|
||||
}
|
||||
|
||||
func errSwarmExists(node *node) error {
|
||||
if node.NodeMembership() != swarmapi.NodeMembershipAccepted {
|
||||
return ErrPendingSwarmExists
|
||||
}
|
||||
return ErrSwarmExists
|
||||
}
|
||||
|
||||
func initClusterSpec(node *node, spec types.Spec) error {
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
for conn := range node.ListenControlSocket(ctx) {
|
||||
if ctx.Err() != nil {
|
||||
@@ -1028,15 +1183,14 @@ func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.Acceptan
|
||||
cluster = lcr.Clusters[0]
|
||||
break
|
||||
}
|
||||
spec := &cluster.Spec
|
||||
|
||||
if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy); err != nil {
|
||||
newspec, err := convert.SwarmSpecToGRPCandMerge(spec, &cluster.Spec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating cluster settings: %v", err)
|
||||
}
|
||||
_, err := client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
|
||||
_, err = client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
|
||||
ClusterID: cluster.ID,
|
||||
ClusterVersion: &cluster.Meta.Version,
|
||||
Spec: spec,
|
||||
Spec: &newspec,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating cluster settings: %v", err)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -3,6 +3,7 @@ package convert
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
@@ -26,14 +27,22 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
||||
HeartbeatTick: c.Spec.Raft.HeartbeatTick,
|
||||
ElectionTick: c.Spec.Raft.ElectionTick,
|
||||
},
|
||||
Dispatcher: types.DispatcherConfig{
|
||||
HeartbeatPeriod: c.Spec.Dispatcher.HeartbeatPeriod,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
heartbeatPeriod, _ := ptypes.Duration(c.Spec.Dispatcher.HeartbeatPeriod)
|
||||
swarm.Spec.Dispatcher.HeartbeatPeriod = uint64(heartbeatPeriod)
|
||||
|
||||
swarm.Spec.CAConfig.NodeCertExpiry, _ = ptypes.Duration(c.Spec.CAConfig.NodeCertExpiry)
|
||||
|
||||
for _, ca := range c.Spec.CAConfig.ExternalCAs {
|
||||
swarm.Spec.CAConfig.ExternalCAs = append(swarm.Spec.CAConfig.ExternalCAs, &types.ExternalCA{
|
||||
Protocol: types.ExternalCAProtocol(strings.ToLower(ca.Protocol.String())),
|
||||
URL: ca.URL,
|
||||
Options: ca.Options,
|
||||
})
|
||||
}
|
||||
|
||||
// Meta
|
||||
swarm.Version.Index = c.Meta.Version.Index
|
||||
swarm.CreatedAt, _ = ptypes.Timestamp(c.Meta.CreatedAt)
|
||||
@@ -49,7 +58,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
||||
Autoaccept: policy.Autoaccept,
|
||||
}
|
||||
if policy.Secret != nil {
|
||||
p.Secret = string(policy.Secret.Data)
|
||||
secret := string(policy.Secret.Data)
|
||||
p.Secret = &secret
|
||||
}
|
||||
swarm.Spec.AcceptancePolicy.Policies = append(swarm.Spec.AcceptancePolicy.Policies, p)
|
||||
}
|
||||
@@ -57,8 +67,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
||||
return swarm
|
||||
}
|
||||
|
||||
// SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec.
|
||||
func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
|
||||
// SwarmSpecToGRPCandMerge converts a Spec to a grpc ClusterSpec and merge AcceptancePolicy from an existing grpc ClusterSpec if provided.
|
||||
func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (swarmapi.ClusterSpec, error) {
|
||||
spec := swarmapi.ClusterSpec{
|
||||
Annotations: swarmapi.Annotations{
|
||||
Name: s.Name,
|
||||
@@ -75,22 +85,37 @@ func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
|
||||
ElectionTick: s.Raft.ElectionTick,
|
||||
},
|
||||
Dispatcher: swarmapi.DispatcherConfig{
|
||||
HeartbeatPeriod: s.Dispatcher.HeartbeatPeriod,
|
||||
HeartbeatPeriod: ptypes.DurationProto(time.Duration(s.Dispatcher.HeartbeatPeriod)),
|
||||
},
|
||||
CAConfig: swarmapi.CAConfig{
|
||||
NodeCertExpiry: ptypes.DurationProto(s.CAConfig.NodeCertExpiry),
|
||||
},
|
||||
}
|
||||
|
||||
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy); err != nil {
|
||||
for _, ca := range s.CAConfig.ExternalCAs {
|
||||
protocol, ok := swarmapi.ExternalCA_CAProtocol_value[strings.ToUpper(string(ca.Protocol))]
|
||||
if !ok {
|
||||
return swarmapi.ClusterSpec{}, fmt.Errorf("invalid protocol: %q", ca.Protocol)
|
||||
}
|
||||
spec.CAConfig.ExternalCAs = append(spec.CAConfig.ExternalCAs, &swarmapi.ExternalCA{
|
||||
Protocol: swarmapi.ExternalCA_CAProtocol(protocol),
|
||||
URL: ca.URL,
|
||||
Options: ca.Options,
|
||||
})
|
||||
}
|
||||
|
||||
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil {
|
||||
return swarmapi.ClusterSpec{}, err
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy.
|
||||
func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy) error {
|
||||
func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error {
|
||||
spec.AcceptancePolicy.Policies = nil
|
||||
hashs := make(map[string][]byte)
|
||||
|
||||
for _, p := range acceptancePolicy.Policies {
|
||||
role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))]
|
||||
if !ok {
|
||||
@@ -102,11 +127,24 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic
|
||||
Autoaccept: p.Autoaccept,
|
||||
}
|
||||
|
||||
if p.Secret != "" {
|
||||
hashPwd, _ := bcrypt.GenerateFromPassword([]byte(p.Secret), 0)
|
||||
if p.Secret != nil {
|
||||
if *p.Secret == "" { // if provided secret is empty, it means erase previous secret.
|
||||
policy.Secret = nil
|
||||
} else { // if provided secret is not empty, we generate a new one.
|
||||
hashPwd, ok := hashs[*p.Secret]
|
||||
if !ok {
|
||||
hashPwd, _ = bcrypt.GenerateFromPassword([]byte(*p.Secret), 0)
|
||||
hashs[*p.Secret] = hashPwd
|
||||
}
|
||||
policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{
|
||||
Data: hashPwd,
|
||||
Alg: "bcrypt",
|
||||
}
|
||||
}
|
||||
} else if oldSecret := getOldSecret(oldSpec, policy.Role); oldSecret != nil { // else use the old one.
|
||||
policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{
|
||||
Data: hashPwd,
|
||||
Alg: "bcrypt",
|
||||
Data: oldSecret.Data,
|
||||
Alg: oldSecret.Alg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,3 +152,15 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOldSecret(oldSpec *swarmapi.ClusterSpec, role swarmapi.NodeRole) *swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret {
|
||||
if oldSpec == nil {
|
||||
return nil
|
||||
}
|
||||
for _, p := range oldSpec.AcceptancePolicy.Policies {
|
||||
if p.Role == role {
|
||||
return p.Secret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -38,8 +38,13 @@ func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapt
|
||||
}
|
||||
|
||||
func (c *containerAdapter) pullImage(ctx context.Context) error {
|
||||
spec := c.container.spec()
|
||||
|
||||
// if the image needs to be pulled, the auth config will be retrieved and updated
|
||||
encodedAuthConfig := c.container.task.ServiceAnnotations.Labels[fmt.Sprintf("%v.registryauth", systemLabelPrefix)]
|
||||
var encodedAuthConfig string
|
||||
if spec.PullOptions != nil {
|
||||
encodedAuthConfig = spec.PullOptions.RegistryAuth
|
||||
}
|
||||
|
||||
authConfig := &types.AuthConfig{}
|
||||
if encodedAuthConfig != "" {
|
||||
@@ -64,7 +69,7 @@ func (c *containerAdapter) pullImage(ctx context.Context) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
// TOOD(stevvooe): Report this status somewhere.
|
||||
// TODO(stevvooe): Report this status somewhere.
|
||||
logrus.Debugln("pull progress", m)
|
||||
}
|
||||
// if the final stream object contained an error, return it
|
||||
@@ -120,13 +125,12 @@ func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backe
|
||||
return err
|
||||
}
|
||||
|
||||
// Docker daemon currently doesnt support multiple networks in container create
|
||||
// Docker daemon currently doesn't support multiple networks in container create
|
||||
// Connect to all other networks
|
||||
nc := c.container.connectNetworkingConfig()
|
||||
|
||||
if nc != nil {
|
||||
for n, ep := range nc.EndpointsConfig {
|
||||
logrus.Errorf("CONNECT %s : %v", n, ep.IPAMConfig.IPv4Address)
|
||||
if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,7 +162,7 @@ func (c *containerAdapter) inspect(ctx context.Context) (types.ContainerJSON, er
|
||||
// events issues a call to the events API and returns a channel with all
|
||||
// events. The stream of events can be shutdown by cancelling the context.
|
||||
//
|
||||
// A chan struct{} is returned that will be closed if the event procressing
|
||||
// A chan struct{} is returned that will be closed if the event processing
|
||||
// fails and needs to be restarted.
|
||||
func (c *containerAdapter) wait(ctx context.Context) error {
|
||||
return c.backend.ContainerWaitWithContext(ctx, c.container.name())
|
||||
|
||||
@@ -3,11 +3,12 @@ package container
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
||||
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// Explictly use the kernel's default setting for CPU quota of 100ms.
|
||||
// Explicitly use the kernel's default setting for CPU quota of 100ms.
|
||||
// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
|
||||
cpuQuotaPeriod = 100 * time.Millisecond
|
||||
|
||||
@@ -116,8 +117,8 @@ func (c *containerConfig) config() *enginecontainer.Config {
|
||||
// If Command is provided, we replace the whole invocation with Command
|
||||
// by replacing Entrypoint and specifying Cmd. Args is ignored in this
|
||||
// case.
|
||||
config.Entrypoint = append(config.Entrypoint, c.spec().Command[0])
|
||||
config.Cmd = append(config.Cmd, c.spec().Command[1:]...)
|
||||
config.Entrypoint = append(config.Entrypoint, c.spec().Command...)
|
||||
config.Cmd = append(config.Cmd, c.spec().Args...)
|
||||
} else if len(c.spec().Args) > 0 {
|
||||
// In this case, we assume the image has an Entrypoint and Args
|
||||
// specifies the arguments for that entrypoint.
|
||||
@@ -345,9 +346,10 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Creating service config in agent for t = %+v", c.task)
|
||||
logrus.Debugf("Creating service config in agent for t = %+v", c.task)
|
||||
svcCfg := &clustertypes.ServiceConfig{
|
||||
Name: c.task.ServiceAnnotations.Name,
|
||||
Aliases: make(map[string][]string),
|
||||
ID: c.task.ServiceID,
|
||||
VirtualAddresses: make(map[string]*clustertypes.VirtualAddress),
|
||||
}
|
||||
@@ -357,6 +359,9 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
|
||||
// We support only IPv4 virtual IP for now.
|
||||
IPv4: c.virtualIP(na.Network.ID),
|
||||
}
|
||||
if len(na.Aliases) > 0 {
|
||||
svcCfg.Aliases[na.Network.ID] = na.Aliases
|
||||
}
|
||||
}
|
||||
|
||||
if c.task.Endpoint != nil {
|
||||
@@ -399,6 +404,9 @@ func (c *containerConfig) networkCreateRequest(name string) (clustertypes.Networ
|
||||
Driver: na.Network.IPAM.Driver.Name,
|
||||
},
|
||||
Options: na.Network.DriverState.Options,
|
||||
Labels: na.Network.Spec.Annotations.Labels,
|
||||
Internal: na.Network.Spec.Internal,
|
||||
EnableIPv6: na.Network.Spec.Ipv6Enabled,
|
||||
CheckDuplicate: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ package container
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/swarmkit/agent/exec"
|
||||
"github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/log"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@@ -59,7 +59,6 @@ func (r *controller) ContainerStatus(ctx context.Context) (*api.ContainerStatus,
|
||||
|
||||
// Update tasks a recent task update and applies it to the container.
|
||||
func (r *controller) Update(ctx context.Context, t *api.Task) error {
|
||||
log.G(ctx).Warnf("task updates not yet supported")
|
||||
// TODO(stevvooe): While assignment of tasks is idempotent, we do allow
|
||||
// updates of metadata, such as labelling, as well as any other properties
|
||||
// that make sense.
|
||||
@@ -84,31 +83,32 @@ func (r *controller) Prepare(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
if err := r.checkClosed(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.create(ctx, r.backend); err != nil {
|
||||
if isContainerCreateNameConflict(err) {
|
||||
if _, err := r.adapter.inspect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.pullImage(ctx); err != nil {
|
||||
// NOTE(stevvooe): We always try to pull the image to make sure we have
|
||||
// the most up to date version. This will return an error, but we only
|
||||
// log it. If the image truly doesn't exist, the create below will
|
||||
// error out.
|
||||
//
|
||||
// This gives us some nice behavior where we use up to date versions of
|
||||
// mutable tags, but will still run if the old image is available but a
|
||||
// registry is down.
|
||||
//
|
||||
// If you don't want this behavior, lock down your image to an
|
||||
// immutable tag or digest.
|
||||
log.G(ctx).WithError(err).Error("pulling image failed")
|
||||
}
|
||||
|
||||
// container is already created. success!
|
||||
return exec.ErrTaskPrepared
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "No such image") { // todo: better error detection
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.pullImage(ctx); err != nil {
|
||||
if err := r.adapter.create(ctx, r.backend); err != nil {
|
||||
if isContainerCreateNameConflict(err) {
|
||||
if _, err := r.adapter.inspect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue // retry to create the container
|
||||
// container is already created. success!
|
||||
return exec.ErrTaskPrepared
|
||||
}
|
||||
|
||||
break
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -135,7 +135,7 @@ func (r *controller) Start(ctx context.Context) error {
|
||||
}
|
||||
|
||||
if err := r.adapter.start(ctx); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "starting container failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -151,9 +151,6 @@ func (r *controller) Wait(pctx context.Context) error {
|
||||
defer cancel()
|
||||
|
||||
err := r.adapter.wait(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
@@ -165,6 +162,7 @@ func (r *controller) Wait(pctx context.Context) error {
|
||||
if ec, ok := err.(exec.ExitCoder); ok {
|
||||
ee.code = ec.ExitCode()
|
||||
}
|
||||
return ee
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type NetworkCreateResponse struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
// VirtualAddress represents a virtual adress.
|
||||
// VirtualAddress represents a virtual address.
|
||||
type VirtualAddress struct {
|
||||
IPv4 string
|
||||
IPv6 string
|
||||
@@ -31,6 +31,7 @@ type PortConfig struct {
|
||||
type ServiceConfig struct {
|
||||
ID string
|
||||
Name string
|
||||
Aliases map[string][]string
|
||||
VirtualAddresses map[string]*VirtualAddress
|
||||
ExposedPorts []*PortConfig
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/docker/docker/pkg/discovery"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
@@ -27,6 +26,9 @@ const (
|
||||
// maximum number of uploads that
|
||||
// may take place at a time for each push.
|
||||
defaultMaxConcurrentUploads = 5
|
||||
// stockRuntimeName is the reserved name/alias used to represent the
|
||||
// OCI runtime being shipped with the docker daemon package.
|
||||
stockRuntimeName = "runc"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -426,12 +428,12 @@ func ValidateConfiguration(config *Config) error {
|
||||
|
||||
// validate that "default" runtime is not reset
|
||||
if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 {
|
||||
if _, ok := runtimes[types.DefaultRuntimeName]; ok {
|
||||
return fmt.Errorf("runtime name '%s' is reserved", types.DefaultRuntimeName)
|
||||
if _, ok := runtimes[stockRuntimeName]; ok {
|
||||
return fmt.Errorf("runtime name '%s' is reserved", stockRuntimeName)
|
||||
}
|
||||
}
|
||||
|
||||
if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != types.DefaultRuntimeName {
|
||||
if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" && defaultRuntime != stockRuntimeName {
|
||||
runtimes := config.GetAllRuntimes()
|
||||
if _, ok := runtimes[defaultRuntime]; !ok {
|
||||
return fmt.Errorf("specified default runtime '%s' does not exist", defaultRuntime)
|
||||
|
||||
@@ -88,8 +88,8 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
|
||||
cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerd socket"))
|
||||
cmd.BoolVar(&config.LiveRestore, []string{"-live-restore"}, false, usageFn("Enable live restore of docker when containers are still running"))
|
||||
config.Runtimes = make(map[string]types.Runtime)
|
||||
cmd.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes), []string{"-add-runtime"}, usageFn("Register an additional OCI compatible runtime"))
|
||||
cmd.StringVar(&config.DefaultRuntime, []string{"-default-runtime"}, types.DefaultRuntimeName, usageFn("Default OCI runtime to be used"))
|
||||
cmd.Var(runconfigopts.NewNamedRuntimeOpt("runtimes", &config.Runtimes, stockRuntimeName), []string{"-add-runtime"}, usageFn("Register an additional OCI compatible runtime"))
|
||||
cmd.StringVar(&config.DefaultRuntime, []string{"-default-runtime"}, stockRuntimeName, usageFn("Default OCI runtime to be used"))
|
||||
|
||||
config.attachExperimentalFlags(cmd, usageFn)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ func (config *Config) GetAllRuntimes() map[string]types.Runtime {
|
||||
}
|
||||
|
||||
func (config *Config) isSwarmCompatible() error {
|
||||
if config.IsValueSet("cluster-store") || config.IsValueSet("cluster-advertise") {
|
||||
if config.ClusterStore != "" || config.ClusterAdvertise != "" {
|
||||
return fmt.Errorf("--cluster-store and --cluster-advertise daemon configurations are incompatible with swarm mode")
|
||||
}
|
||||
if config.LiveRestore {
|
||||
|
||||
@@ -50,7 +50,7 @@ func (config *Config) GetRuntime(name string) *types.Runtime {
|
||||
|
||||
// GetDefaultRuntimeName returns the current default runtime
|
||||
func (config *Config) GetDefaultRuntimeName() string {
|
||||
return types.DefaultRuntimeName
|
||||
return stockRuntimeName
|
||||
}
|
||||
|
||||
// GetAllRuntimes returns a copy of the runtimes map
|
||||
|
||||
@@ -408,7 +408,21 @@ func (daemon *Daemon) allocateNetwork(container *container.Container) error {
|
||||
updateSettings = true
|
||||
}
|
||||
|
||||
// always connect default network first since only default
|
||||
// network mode support link and we need do some setting
|
||||
// on sandbox initialize for link, but the sandbox only be initialized
|
||||
// on first network connecting.
|
||||
defaultNetName := runconfig.DefaultDaemonNetworkMode().NetworkName()
|
||||
if nConf, ok := container.NetworkSettings.Networks[defaultNetName]; ok {
|
||||
if err := daemon.connectToNetwork(container, defaultNetName, nConf, updateSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
for n, nConf := range container.NetworkSettings.Networks {
|
||||
if n == defaultNetName {
|
||||
continue
|
||||
}
|
||||
if err := daemon.connectToNetwork(container, n, nConf, updateSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ func (daemon *Daemon) restore() error {
|
||||
logrus.Errorf("Failed to restore with containerd: %q", err)
|
||||
return
|
||||
}
|
||||
if !c.HostConfig.NetworkMode.IsContainer() {
|
||||
if !c.HostConfig.NetworkMode.IsContainer() && c.IsRunning() {
|
||||
options, err := daemon.buildSandboxOptions(c)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed build sandbox option to restore container %s: %v", c.ID, err)
|
||||
@@ -385,6 +385,11 @@ func (daemon *Daemon) IsSwarmCompatible() error {
|
||||
func NewDaemon(config *Config, registryService registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
|
||||
setDefaultMtu(config)
|
||||
|
||||
// Ensure that we have a correct root key limit for launching containers.
|
||||
if err := ModifyRootKeyLimit(); err != nil {
|
||||
logrus.Warnf("unable to modify root key limit, number of containers could be limitied by this quota: %v", err)
|
||||
}
|
||||
|
||||
// Ensure we have compatible and valid configuration options
|
||||
if err := verifyDaemonSettings(config); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -532,7 +532,7 @@ func (daemon *Daemon) platformReload(config *Config, attributes *map[string]stri
|
||||
if config.IsValueSet("runtimes") {
|
||||
daemon.configStore.Runtimes = config.Runtimes
|
||||
// Always set the default one
|
||||
daemon.configStore.Runtimes[types.DefaultRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
|
||||
daemon.configStore.Runtimes[stockRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
|
||||
}
|
||||
|
||||
if config.DefaultRuntime != "" {
|
||||
@@ -574,12 +574,12 @@ func verifyDaemonSettings(config *Config) error {
|
||||
}
|
||||
|
||||
if config.DefaultRuntime == "" {
|
||||
config.DefaultRuntime = types.DefaultRuntimeName
|
||||
config.DefaultRuntime = stockRuntimeName
|
||||
}
|
||||
if config.Runtimes == nil {
|
||||
config.Runtimes = make(map[string]types.Runtime)
|
||||
}
|
||||
config.Runtimes[types.DefaultRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
|
||||
config.Runtimes[stockRuntimeName] = types.Runtime{Path: DefaultRuntimeBinary}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -197,6 +197,8 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
|
||||
defer PutDriver(t)
|
||||
base := stringid.GenerateRandomID()
|
||||
upper := stringid.GenerateRandomID()
|
||||
deleteFile := "file-remove.txt"
|
||||
deleteFileContent := []byte("This file should get removed in upper!")
|
||||
|
||||
if err := driver.Create(base, "", "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -206,6 +208,10 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := addFile(driver, base, deleteFile, deleteFileContent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := driver.Create(upper, base, "", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -213,6 +219,11 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
|
||||
if err := addManyFiles(driver, upper, fileCount, 6); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := removeFile(driver, upper, deleteFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
diffSize, err := driver.DiffSize(upper, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -227,6 +238,10 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := checkFile(driver, diff, deleteFile, deleteFileContent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
arch, err := driver.Diff(upper, base)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -248,9 +263,14 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
|
||||
if applyDiffSize != diffSize {
|
||||
t.Fatalf("Apply diff size different, got %d, expected %d", applyDiffSize, diffSize)
|
||||
}
|
||||
|
||||
if err := checkManyFiles(driver, diff, fileCount, 6); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := checkFileRemoved(driver, diff, deleteFile); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// DriverTestChanges tests computed changes on a layer matches changes made
|
||||
|
||||
@@ -78,6 +78,32 @@ func addFile(drv graphdriver.Driver, layer, filename string, content []byte) err
|
||||
return ioutil.WriteFile(path.Join(root, filename), content, 0755)
|
||||
}
|
||||
|
||||
func removeFile(drv graphdriver.Driver, layer, filename string) error {
|
||||
root, err := drv.Get(layer, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drv.Put(layer)
|
||||
|
||||
return os.Remove(path.Join(root, filename))
|
||||
}
|
||||
|
||||
func checkFileRemoved(drv graphdriver.Driver, layer, filename string) error {
|
||||
root, err := drv.Get(layer, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer drv.Put(layer)
|
||||
|
||||
if _, err := os.Stat(path.Join(root, filename)); err == nil {
|
||||
return fmt.Errorf("file still exists: %s", path.Join(root, filename))
|
||||
} else if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addManyFiles(drv graphdriver.Driver, layer string, count int, seed int64) error {
|
||||
root, err := drv.Get(layer, "")
|
||||
if err != nil {
|
||||
|
||||
@@ -32,12 +32,6 @@ type mountOptions struct {
|
||||
}
|
||||
|
||||
func mountFrom(dir, device, target, mType, label string) error {
|
||||
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mountfrom pipe failure: %v", err)
|
||||
}
|
||||
|
||||
options := &mountOptions{
|
||||
Device: device,
|
||||
Target: target,
|
||||
@@ -47,7 +41,10 @@ func mountFrom(dir, device, target, mType, label string) error {
|
||||
}
|
||||
|
||||
cmd := reexec.Command("docker-mountfrom", dir)
|
||||
cmd.Stdin = r
|
||||
w, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("mountfrom error on pipe creation: %v", err)
|
||||
}
|
||||
|
||||
output := bytes.NewBuffer(nil)
|
||||
cmd.Stdout = output
|
||||
|
||||
@@ -114,19 +114,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
||||
backingFs = fsName
|
||||
}
|
||||
|
||||
// check if they are running over btrfs, aufs, zfs or overlay
|
||||
// check if they are running over btrfs, aufs, zfs, overlay, or ecryptfs
|
||||
switch fsMagic {
|
||||
case graphdriver.FsMagicBtrfs:
|
||||
logrus.Error("'overlay' is not supported over btrfs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicAufs:
|
||||
logrus.Error("'overlay' is not supported over aufs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicZfs:
|
||||
logrus.Error("'overlay' is not supported over zfs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicOverlay:
|
||||
logrus.Error("'overlay' is not supported over overlay.")
|
||||
case graphdriver.FsMagicBtrfs, graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs:
|
||||
logrus.Errorf("'overlay2' is not supported over %s", backingFs)
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdri
|
||||
return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
|
||||
}
|
||||
|
||||
if err := mount.MakePrivate(base); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
dataset: rootDataset,
|
||||
options: options,
|
||||
|
||||
@@ -104,27 +104,34 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
||||
|
||||
repoRefs = daemon.referenceStore.References(imgID)
|
||||
|
||||
// If this is a tag reference and all the remaining references
|
||||
// to this image are digest references, delete the remaining
|
||||
// references so that they don't prevent removal of the image.
|
||||
// If a tag reference was removed and the only remaining
|
||||
// references to the same repository are digest references,
|
||||
// then clean up those digest references.
|
||||
if _, isCanonical := parsedRef.(reference.Canonical); !isCanonical {
|
||||
foundTagRef := false
|
||||
foundRepoTagRef := false
|
||||
for _, repoRef := range repoRefs {
|
||||
if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical {
|
||||
foundTagRef = true
|
||||
if _, repoRefIsCanonical := repoRef.(reference.Canonical); !repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
||||
foundRepoTagRef = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundTagRef {
|
||||
if !foundRepoTagRef {
|
||||
// Remove canonical references from same repository
|
||||
remainingRefs := []reference.Named{}
|
||||
for _, repoRef := range repoRefs {
|
||||
if _, err := daemon.removeImageRef(repoRef); err != nil {
|
||||
return records, err
|
||||
}
|
||||
if _, repoRefIsCanonical := repoRef.(reference.Canonical); repoRefIsCanonical && parsedRef.Name() == repoRef.Name() {
|
||||
if _, err := daemon.removeImageRef(repoRef); err != nil {
|
||||
return records, err
|
||||
}
|
||||
|
||||
untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
||||
records = append(records, untaggedRecord)
|
||||
untaggedRecord := types.ImageDelete{Untagged: repoRef.String()}
|
||||
records = append(records, untaggedRecord)
|
||||
} else {
|
||||
remainingRefs = append(remainingRefs, repoRef)
|
||||
|
||||
}
|
||||
}
|
||||
repoRefs = []reference.Named{}
|
||||
repoRefs = remainingRefs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,11 +142,10 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
||||
|
||||
removedRepositoryRef = true
|
||||
} else {
|
||||
// If an ID reference was given AND there is exactly one
|
||||
// repository reference to the image then we will want to
|
||||
// remove that reference.
|
||||
// FIXME: Is this the behavior we want?
|
||||
if len(repoRefs) == 1 {
|
||||
// If an ID reference was given AND there is at most one tag
|
||||
// reference to the image AND all references are within one
|
||||
// repository, then remove all references.
|
||||
if isSingleReference(repoRefs) {
|
||||
c := conflictHard
|
||||
if !force {
|
||||
c |= conflictSoft &^ conflictActiveReference
|
||||
@@ -148,21 +154,48 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
|
||||
return nil, conflict
|
||||
}
|
||||
|
||||
parsedRef, err := daemon.removeImageRef(repoRefs[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for _, repoRef := range repoRefs {
|
||||
parsedRef, err := daemon.removeImageRef(repoRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
||||
|
||||
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
||||
records = append(records, untaggedRecord)
|
||||
}
|
||||
|
||||
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
||||
|
||||
daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
|
||||
records = append(records, untaggedRecord)
|
||||
}
|
||||
}
|
||||
|
||||
return records, daemon.imageDeleteHelper(imgID, &records, force, prune, removedRepositoryRef)
|
||||
}
|
||||
|
||||
// isSingleReference returns true when all references are from one repository
|
||||
// and there is at most one tag. Returns false for empty input.
|
||||
func isSingleReference(repoRefs []reference.Named) bool {
|
||||
if len(repoRefs) <= 1 {
|
||||
return len(repoRefs) == 1
|
||||
}
|
||||
var singleRef reference.Named
|
||||
canonicalRefs := map[string]struct{}{}
|
||||
for _, repoRef := range repoRefs {
|
||||
if _, isCanonical := repoRef.(reference.Canonical); isCanonical {
|
||||
canonicalRefs[repoRef.Name()] = struct{}{}
|
||||
} else if singleRef == nil {
|
||||
singleRef = repoRef
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if singleRef == nil {
|
||||
// Just use first canonical ref
|
||||
singleRef = repoRefs[0]
|
||||
}
|
||||
_, ok := canonicalRefs[singleRef.Name()]
|
||||
return len(canonicalRefs) == 1 && ok
|
||||
}
|
||||
|
||||
// isImageIDPrefix returns whether the given possiblePrefix is a prefix of the
|
||||
// given imageID.
|
||||
func isImageIDPrefix(imageID, possiblePrefix string) bool {
|
||||
|
||||
59
daemon/keys.go
Normal file
59
daemon/keys.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// +build linux
|
||||
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
rootKeyFile = "/proc/sys/kernel/keys/root_maxkeys"
|
||||
rootBytesFile = "/proc/sys/kernel/keys/root_maxbytes"
|
||||
rootKeyLimit = 1000000
|
||||
// it is standard configuration to allocate 25 bytes per key
|
||||
rootKeyByteMultiplier = 25
|
||||
)
|
||||
|
||||
// ModifyRootKeyLimit checks to see if the root key limit is set to
|
||||
// at least 1000000 and changes it to that limit along with the maxbytes
|
||||
// allocated to the keys at a 25 to 1 multiplier.
|
||||
func ModifyRootKeyLimit() error {
|
||||
value, err := readRootKeyLimit(rootKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if value < rootKeyLimit {
|
||||
return setRootKeyLimit(rootKeyLimit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setRootKeyLimit(limit int) error {
|
||||
keys, err := os.OpenFile(rootKeyFile, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer keys.Close()
|
||||
if _, err := fmt.Fprintf(keys, "%d", limit); err != nil {
|
||||
return err
|
||||
}
|
||||
bytes, err := os.OpenFile(rootBytesFile, os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer bytes.Close()
|
||||
_, err = fmt.Fprintf(bytes, "%d", limit*rootKeyByteMultiplier)
|
||||
return err
|
||||
}
|
||||
|
||||
func readRootKeyLimit(path string) (int, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return strconv.Atoi(strings.Trim(string(data), "\n"))
|
||||
}
|
||||
8
daemon/keys_unsupported.go
Normal file
8
daemon/keys_unsupported.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !linux
|
||||
|
||||
package daemon
|
||||
|
||||
// ModifyRootKeyLimit is an noop on unsupported platforms.
|
||||
func ModifyRootKeyLimit() error {
|
||||
return nil
|
||||
}
|
||||
@@ -465,6 +465,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
|
||||
GlobalIPv6Address: network.GlobalIPv6Address,
|
||||
GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
|
||||
MacAddress: network.MacAddress,
|
||||
NetworkID: network.NetworkID,
|
||||
}
|
||||
if network.IPAMConfig != nil {
|
||||
networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{
|
||||
|
||||
@@ -14,10 +14,11 @@ import (
|
||||
// Writes are concurrent, so you need implement some sync in your logger
|
||||
type Copier struct {
|
||||
// srcs is map of name -> reader pairs, for example "stdout", "stderr"
|
||||
srcs map[string]io.Reader
|
||||
dst Logger
|
||||
copyJobs sync.WaitGroup
|
||||
closed chan struct{}
|
||||
srcs map[string]io.Reader
|
||||
dst Logger
|
||||
copyJobs sync.WaitGroup
|
||||
closeOnce sync.Once
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
// NewCopier creates a new Copier
|
||||
@@ -74,9 +75,7 @@ func (c *Copier) Wait() {
|
||||
|
||||
// Close closes the copier
|
||||
func (c *Copier) Close() {
|
||||
select {
|
||||
case <-c.closed:
|
||||
default:
|
||||
c.closeOnce.Do(func() {
|
||||
close(c.closed)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user