Compare commits

...

301 Commits

Author SHA1 Message Date
af2647d55b Merge pull request #1634 from thaJeztah/18.09_bump_golang_1.10.8
[18.09] Bump Golang 1.10.8 (CVE-2019-6486)
2019-01-24 14:27:59 +01:00
c71aa11c0a [18.09] Bump Golang 1.10.8 (CVE-2019-6486)
See the milestone for details;
https://github.com/golang/go/issues?q=milestone%3AGo1.10.8+label%3ACherryPickApproved

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2019-01-24 02:07:03 +01:00
336b2a5cac Merge pull request #1580 from thaJeztah/18.09_backport_e2e-invocation-nit
[18.09 backport] e2e updates
2018-12-19 14:20:03 +01:00
c462e06fcd e2e: assign a default value of 0 to DOCKERD_EXPERIMENTAL
Currently running the e2e tests produces a warning/error:

    $ make -f docker.Makefile test-e2e
    «...»
    docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker-cli-e2e
    ./scripts/test/e2e/run: line 20: test: : integer expression expected

This is from:

    test "${DOCKERD_EXPERIMENTAL:-}" -eq "1" && «...»

Where `${DOCKERD_EXPERIMENTAL:-}` expands to the empty string, resulting in
`test "" -eq "1"` which produces the warning. This error is enough to trigger
the short-circuiting behaviour of `&&` so the result is as expected, but fix
the issue nonetheless by provdiing a default `0`.

Signed-off-by: Ian Campbell <ijc@docker.com>
(cherry picked from commit 4f483276cf)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-17 17:23:04 +01:00
719508a935 connhelper: add e2e
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit 9b148db87a)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-17 17:22:58 +01:00
2fa3aae9ed Merge pull request #1575 from thaJeztah/bump_golang_1.10.6
[18.09] Bump Golang 1.10.6 (CVE-2018-16875)
2018-12-14 20:56:04 +00:00
6c3a10aaed Bump Golang 1.10.6 (CVE-2018-16875)
go1.10.6 (released 2018/12/14)

- crypto/x509: CPU denial of service in chain validation golang/go#29233
- cmd/go: directory traversal in "go get" via curly braces in import paths golang/go#29231
- cmd/go: remote command execution during "go get -u" golang/go#29230

See the Go 1.10.6 milestone on the issue tracker for details:
https://github.com/golang/go/issues?q=milestone%3AGo1.10.6

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-14 01:41:33 +01:00
3ee6755815 Merge pull request #1567 from thaJeztah/18.09_backport_fix_panic_on_update
[18.09 backport] Fix panic (npe) when updating service limits/reservations
2018-12-13 10:39:37 +00:00
16349f6e33 Fix panic (npe) when updating service limits/reservations
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 579bb91853)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-13 02:22:43 +01:00
2aa77af30f Merge pull request #1554 from thaJeztah/18.09_backport_completion-import--platform
[18.09 backport] Add bash completion for `import --platform`
2018-12-07 13:10:27 -08:00
456c1ce695 Merge pull request #1553 from thaJeztah/18.09_backport_completion-log-driver-local
[18.09 backport] Add bash completion for "local" log driver
2018-12-07 13:10:06 -08:00
bcadc9061c Add bash completion for import --platform
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit e0fe546c37)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-07 20:08:35 +01:00
e05745b4a5 Add bash completion for "local" log driver
Ref: https://github.com/moby/moby/pull/37092

Also adds log-opt `compress` to json-file log driver because this was
also added in the referenced PR.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit c59038b15c)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-07 20:05:08 +01:00
b6ecef353f Merge pull request #1552 from thaJeztah/18.09_backport_fix_filter_panic
[18.09 backport] Fix panic when pruning images with label-filter
2018-12-07 19:29:32 +01:00
e380ddaddf Fix panic when pruning images with label-filter
Before this change:

    docker image prune --force --filter "label=foobar"
    panic: assignment to entry in nil map

    goroutine 1 [running]:
    github.com/docker/cli/vendor/github.com/docker/docker/api/types/filters.Args.Add(...)
    /go/src/github.com/docker/cli/vendor/github.com/docker/docker/api/types/filters/parse.go:167
    github.com/docker/cli/cli/command/image.runPrune(0x1db3a20, 0xc000344cf0, 0x16e0001, 0xc00015e600, 0x4, 0x3, 0xc00024e160, 0xc000545c70, 0x5ab4b5)
    /go/src/github.com/docker/cli/cli/command/image/prune.go:79 +0xbaf
    github.com/docker/cli/cli/command/image.NewPruneCommand.func1(0xc00029ef00, 0xc0004a8180, 0x0, 0x3, 0x0, 0x0)
    /go/src/github.com/docker/cli/cli/command/image/prune.go:32 +0x64
    github.com/docker/cli/vendor/github.com/spf13/cobra.(*Command).execute(0xc00029ef00, 0xc000038210, 0x3, 0x3, 0xc00029ef00, 0xc000038210)
    /go/src/github.com/docker/cli/vendor/github.com/spf13/cobra/command.go:762 +0x473
    github.com/docker/cli/vendor/github.com/spf13/cobra.(*Command).ExecuteC(0xc000127180, 0xc000272770, 0x1836ce0, 0xc000272780)
    /go/src/github.com/docker/cli/vendor/github.com/spf13/cobra/command.go:852 +0x2fd
    github.com/docker/cli/vendor/github.com/spf13/cobra.(*Command).Execute(0xc000127180, 0xc000127180, 0x1d60880)
    /go/src/github.com/docker/cli/vendor/github.com/spf13/cobra/command.go:800 +0x2b
    main.main()
    /go/src/github.com/docker/cli/cmd/docker/docker.go:180 +0xdc

With this patch applied:

    docker image prune --force --filter "label=foobar"
    Total reclaimed space: 0B

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 1e1dd5bca4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-12-07 17:58:38 +01:00
12834eeff6 Merge pull request #1542 from thaJeztah/18.09_backport_completion_cli_experimental
[18.09 backport] Add bash completion for experimental CLI commands (manifest)
2018-12-03 13:34:56 -08:00
bb46da9fba Merge pull request #1544 from thaJeztah/18.09_bump_go_to_1.10.5
[18.09] Bump Go to 1.10.5
2018-11-30 14:03:12 -08:00
871d24d3fc Bump Go to 1.10.5
go1.10.5 (released 2018/11/02) includes fixes to the go command, linker,
runtime and the database/sql package. See the milestone on the issue
tracker for details:

List of changes; https://github.com/golang/go/issues?q=milestone%3AGo1.10.5

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-30 21:59:54 +01:00
61a9096b8d Merge pull request #1540 from thaJeztah/18.09_backport_fix_flags_in_usage
[18.09 backport] Fix yamldocs outputing `[flags]` in usage output
2018-11-29 13:26:27 -08:00
2ac475cf97 Add bash completion for manifest command family
Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 0fb4256a00)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-29 17:15:06 +01:00
2a36695037 Add support for experimental cli features to bash completion
This is needed for implementing bash completion for the `docker manifest`
command family.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit a183c952c6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-29 17:15:04 +01:00
dc74fc81f2 Refactor usage of docker version in bash completion
This preapares bash completion for more context sensitivity:

- experimental cli features
- orchestrator specific features

Also renames _daemon_ to _server_ where used in context of `docker version`
because the fields there are grouped unter _Server_.

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 564d4da06e)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-29 17:15:02 +01:00
7e90635652 Fix yamldocs outputing [flags] in usage output
A similar change was made in the CLI itself, but is not
inherited by the code that generates the YAML docs.

Before this patch is applied;

```
usage: docker container exec [OPTIONS] CONTAINER COMMAND [ARG...] [flags]
```

With this patch applied:

```
usage: docker container exec [OPTIONS] CONTAINER COMMAND [ARG...]
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 44d96e9120)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-29 15:44:16 +01:00
3f7989903a Merge pull request #1454 from thaJeztah/18.09_backport_defaulttcpschema
[18.09 backport] fixes #1441 set default schema to tcp for docker host
2018-11-27 09:32:51 -08:00
7059d069c3 Merge pull request #1532 from tiborvass/18.09-fix-system-prune-filters
[18.09] prune: move image pruning before build cache pruning
2018-11-26 16:07:21 -08:00
4a4a1f3615 prune: move image pruning before build cache pruning
This is cleaner because running system prune twice in a row
now results in a no-op the second time.

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 6c10abb247)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-11-21 22:01:54 +00:00
1274f23252 Merge pull request #1531 from thaJeztah/18.09_backport_builder_docs
[18.09 backport] builder documentation updates
2018-11-21 18:10:29 +01:00
3af1848dda buildkit reference docs
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
Signed-off-by: Tibor Vass <tibor@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 83aeb219f0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-21 17:16:30 +01:00
6d91f5d55d Documenting ENTRYPOINT can empty value of CMD
Signed-off-by: Brandon Mitchell <git@bmitch.net>
(cherry picked from commit cc316fde55)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-21 17:16:24 +01:00
d56948c12c Merge pull request #1530 from thaJeztah/18.09_backport_add_logging_driver_example
[18.09 backport] Update daemon.json example to show that log-opts must be a string
2018-11-21 17:10:02 +01:00
9b3eea87ee Update daemon.json example to show that log-opts must be a string
log-opts are passed to logging-drivers as-is, so the daemon is not
aware what value-type each option takes.

For this reason, all options must be provided as a string, even if
they are used as numeric values by the logging driver.

For example, to pass the "max-file" option to the default (json-file)
logging driver, this value has to be passed as a string;

```json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
```

When passed as a _number_ (`"max-file": 3`), the daemon will invalidate
the configuration file, and fail to start;

    unable to configure the Docker daemon with file /etc/docker/daemon.json: json: cannot unmarshal number into Go value of type string

This patch adds an example to the daemon.json to show these  values
have to be passed as strings.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit fd33e0d933)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-21 15:34:41 +01:00
31c092e155 Merge pull request #1526 from thaJeztah/18.09_backport_completion_fix_service__force
[18.09 backport] Fix bash completion for `service update --force`
2018-11-21 11:38:28 +01:00
046ffa4e87 Fix bash completion for service update --force
- `--force` is not available in `service create`
- `--force` is a boolean option

Signed-off-by: Harald Albers <github@albersweb.de>
(cherry picked from commit 5fa5eb1da6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-11-20 18:18:33 +01:00
51668a30f2 Merge pull request #1483 from thaJeztah/18.09_backport_docs_fixes
[18.09 backport] various docs fixes
2018-10-26 15:54:26 +01:00
5e7f9d3c84 docs, ssh: unsupport password auth explicitly
The issue with password auth is tracked in #1476 and #1477 .

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit 16b014e062)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:33 +02:00
72ddefbada Documenting example default-ulimit in daemon.json
Signed-off-by: Brandon Mitchell <git@bmitch.net>
(cherry picked from commit 3f4f450941)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:31 +02:00
135aa72476 Updating rmi doc example to specify latest tag
Signed-off-by: Brandon Mitchell <git@bmitch.net>
(cherry picked from commit f913b73c81)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:28 +02:00
7c7fe26a6f Minor typo fix in run documentation
Quick syntax fix!

Signed-off-by: Scott Brenner <scott@scottbrenner.me>
(cherry picked from commit 50143cff12)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:26 +02:00
1df47ffb4d Clarify in docs that docker tag doesn't publish
I am attempting to push a tag to a private repository. The documentation for `docker tag`  has an explicit example to for how ["To push an image to a private registry"](https://docs.docker.com/engine/reference/commandline/tag/#tag-an-image-referenced-by-name). My colleague clarified that this command does not in fact push anything, so I thought this PR might save some future novice the same confusion.

Signed-off-by: Jake Lambert <jake.lambert@volusion.com>
(cherry picked from commit 4ed484bac4)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:24 +02:00
2e7e529a18 Fix login documentation layout
ddadd3db49 mass standardized the
formatting, with some errors.

This commit fixes errors on `login.md`:
- revert wrong `Logging out` headline
- restore correct level for some headlines (relative to parent
  headline level change)
- re-add `Usage` headlines, with better name

Also add `related commands` headline on `login` and `logout`.

Signed-off-by: Thomas Riccardi <thomas@deepomatic.com>
(cherry picked from commit a0e3ec8790)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:22 +02:00
f8f230181e Docs: Add Spaces Around Parenthesis Where Needed
Signed-off-by: Alex Mayer <amayer5125@gmail.com>
(cherry picked from commit 2b0fdd0f17)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:19 +02:00
0ee4693953 Typo fix
Signed-off-by: Lihua Tang <lhtang@alauda.io>
(cherry picked from commit ca5e453180)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:17 +02:00
cb4cd04c64 Typo fix: filesytem -> filesystem
Signed-off-by: Rui Cao <ruicao@alauda.io>
(cherry picked from commit 2eb95909ee)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:15 +02:00
d2e771fed6 update docs with current supported features options
Signed-off-by: Anda Xu <anda.xu@docker.com>
(cherry picked from commit d656706678)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-25 15:30:09 +02:00
b8911a3b33 Merge pull request #1481 from thaJeztah/18.09_backport_fix_docker_invalid_subcommand
[18.09 backport] Fix `docker invalid-subcommand` regression
2018-10-25 09:35:28 +02:00
ebe071a9b3 Fix docker invalid-subcommand regression
Starting with a3fe7d62b8,
`docker invalid-subcommand` did not exit with non-zero status.

Fix #1428

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit d708cada43)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-24 20:49:10 +02:00
ecb972ab38 Merge pull request #1475 from tiborvass/18.09-fix-build-stream
[18.09] build: update session support detection
2018-10-22 19:24:24 -07:00
4c68a9666f build: update session support detection
Avoid testing for session support in non-buildkit builder to support
servers that falsely report as `1.39` compatible

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit 3e8c41beb0)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-10-22 23:02:08 +00:00
e245b72381 Merge pull request #1458 from dhiltgen/bump_licensing_lib
[18.09] Bump licensing lib
2018-10-19 13:58:37 -07:00
0ff9e5cd10 Remove e-mail from trial flow
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-18 14:56:59 -07:00
8e565d0399 Bump licensing library
Removes the billing profile flow which is now handled on the back-end.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-18 14:44:05 -07:00
8a424333f9 Merge pull request #1455 from thaJeztah/18.09_backport_legacy_drivers
[18.09 backport] deprecate devicemapper and legacy overlay storage drivers
2018-10-18 00:46:50 +02:00
fde819236b Deprecate "devicemapper" storage driver.
The `devicemapper` storage driver is deprecated in favor of `overlay2`, and will
be removed in a future release. Users of the `devicemapper` storage driver are
recommended to migrate to a different storage driver, such as `overlay2`, which
is now the default storage driver.

The `devicemapper` storage driver facilitates running Docker on older (3.x) kernels
that have no support for other storage drivers (such as overlay2, or AUFS).

Now that support for `overlay2` is added to all supported distros (as they are
either on kernel 4.x, or have support for multiple lowerdirs backported), there
is no reason to continue maintenance of the `devicemapper` storage driver.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 662441ba31)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-17 18:04:04 +02:00
aa6314c663 Deprecate legacy overlay storage driver
The `overlay` storage driver is deprecated in favor of the `overlay2` storage
driver, which has all the benefits of `overlay`, without its limitations (excessive
inode consumption). The legacy `overlay` storage driver will be removed in a future
release. Users of the `overlay` storage driver should migrate to the `overlay2`
storage driver.

The legacy `overlay` storage driver allowed using overlayFS-backed filesystems
on pre 4.x kernels. Now that all supported distributions are able to run `overlay2`
(as they are either on kernel 4.x, or have support for multiple lowerdirs
backported), there is no reason to keep maintaining the `overlay` storage driver.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8bc2aa45a6)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-17 18:03:57 +02:00
81ee98e861 Merge pull request #1453 from tiborvass/18.09-builder-prune-filter-unused-for
[18.09 backport] builder/prune: rename max-age filter to unused-for in help output
2018-10-17 17:56:13 +02:00
8ae4453d46 add test case TestNewAPIClientFromFlagsForDefaultSchema
Signed-off-by: Lifubang <lifubang@acmcoder.com>
(cherry picked from commit beed8748c0)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-17 17:48:07 +02:00
aeea559129 set default schema to tcp for docker host
Signed-off-by: Lifubang <lifubang@acmcoder.com>
(cherry picked from commit 2431dd1448)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-17 17:47:58 +02:00
22336b332c Merge pull request #1432 from thaJeztah/18.09_backport_use_string_builder
[18.09] backport using strings.Builder instead of string appending
2018-10-16 19:42:29 -07:00
2961611fda builder/prune: rename max-age filter to unused-for in help output
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit c9ce6dc656)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-10-17 00:37:24 +00:00
17adf05188 Merge pull request #1421 from dhiltgen/final_url
[18.09] Update release note link to final location
2018-10-16 19:42:48 +02:00
39f1110308 Merge pull request #1438 from thaJeztah/18.09_backport_hide-buildkit-flags-if-not-enabled
[18.09 backport] builder / buildkit updates
2018-10-11 15:35:44 -07:00
3dfacb55a4 build: only show buildkit-specific flags if buildkit is enabled
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit bbd01fe3df)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-12 00:16:48 +02:00
e942084530 build: add SSH agent socket forwarder (docker build --ssh $SSHMOUNTID=$SSH_AUTH_SOCK)
Unlike `docker build --secret`, `docker build --ssh` allows the build container to
use SSH keys with passphrases.

  $ eval $(ssh-agent)
  $ ssh-add ~/.ssh/id_rsa
  (Input your passphrase here)
  $ docker build --ssh default=$SSH_AUTH_SOCK ...

This feature requires the daemon with `CapExecMountSSH` build capability (moby/moby#37973) .

Currently, the official Dockerfile frontend does not provide the syntax for using the SSH forwarder.

However, the experimental `RUN --mount=type=ssh` syntax can be enabled by using
the Dockerfile frontend image built with the `BUILDTAGS="dfrunmount dfssh"`, via the `# syntax =` "shebang".

The Dockerfile for the Dockerfile frontend is available at  github.com/moby/buildkit/frontend/dockerfile/cmd/dockerfile-frontend)
The pre-built image is also available as `tonistiigi/dockerfile:ssh20181002` .

An example Dockerfile with `RUN --mount=type=ssh`:

  # syntax = tonistiigi/dockerfile:ssh20181002
  FROM alpine
  RUN apk add --no-cache openssh-client
  RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
  RUN --mount=type=ssh ssh git@gitlab.com | tee /hello
  # "Welcome to GitLab, @GITLAB_USERNAME_ASSOCIATED_WITH_SSHKEY" should be printed here

More info available at moby/buildkit#608, moby/buildkit#655

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit db7399a016)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-12 00:16:42 +02:00
50f529fa47 bump up buildkit
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit 846c38cbd7)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-12 00:16:38 +02:00
b4bee9be75 Bump buildkit and dependencies to 39404586a50d1b9d0fb1c578cf0f4de7bdb7afe5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8cfd24049f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-12 00:16:29 +02:00
8b0d34a5a1 Using strings.Builder instead of string appending
Signed-off-by: Li Yi <denverdino@gmail.com>
(cherry picked from commit 814ced4b30)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-10-10 20:21:52 +02:00
f93908213a Update release note link to final location
We'll be using a redirect from this URL to the back-end docs system for
hosting release notes. Final location confirmed with Docs team and PM.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-08 16:25:49 -07:00
4280972d65 Merge pull request #1402 from AkihiroSuda/fix-kill-warning-1809
[18.09] backport connhelper: try sending SIGTERM before SIGKILL
2018-10-04 16:53:57 +02:00
984bc7411e Merge pull request #1408 from dhiltgen/fix_panic
[18.09] Fix panic in display only case for license
2018-10-02 15:41:22 -07:00
92932647d3 Add test coverage for display only with hub licenses
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-02 11:21:22 -07:00
dee37936e5 Fix panic in display only case for license
Prior refactoring passes missed a corner case.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-02 10:24:16 -07:00
3e1a0bdc23 Merge pull request #1406 from dhiltgen/revamp_18.09
Remove unused helath check func
2018-10-02 11:26:36 +02:00
f2b2061cc3 Remove unused helath check func
During the refactoring for 18.09 the activate/update flows no longer
restart the engine explicitly but let the user do that when they're ready,
so the health check logic is no longer required.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-01 15:12:09 -07:00
4925fd9c34 connhelper: try sending SIGTERM before SIGKILL
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit acbb0eb6da)
Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
2018-09-30 10:01:20 +09:00
5d3ab5bc0c Merge pull request #1388 from dhiltgen/revamp_18.09
[18.09] Refine how metadata dir is handled
2018-09-28 14:23:12 -07:00
c12e23a4c1 Refine how metadata dir is handled
This is a follow up PR to #1381 to address some of the review comments
we didn't get to.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-28 14:14:13 -07:00
aca3f2d382 Merge pull request #1387 from dhiltgen/activate_ux
[18.09] Expose licensing details before loading
2018-09-28 14:02:48 -07:00
a7488d1bcd use gotest.tools/fs for TestActivateExpiredLicenseDryRun
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-09-28 20:50:43 +00:00
5a97a93ae1 Expose licensing details before loading
Help the user understand which license they're about
to load in case they have multiple licenses they need to
figure out.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-28 20:50:43 +00:00
41910b6d68 Vendor bump of licensing lib
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-28 20:50:43 +00:00
1a087e87c9 Merge pull request #1389 from mason-fish/fix-subscription-filter
[18.09] fix subscription filter
2018-09-26 16:12:41 -07:00
0b11120060 Merge pull request #1394 from thaJeztah/18.09_backport_
[18.09] backport fix substitution with non-empty env-var
2018-09-26 15:33:01 +02:00
e57b20642d Merge pull request #1342 from tonistiigi/1809-fix-os-race
[18.09] backport connhelper: fix cmd.Wait() race
2018-09-26 15:30:00 +02:00
b8702b8a9a Fix substitution with non-empty env-var
Due to a typo, substitution would not work if the given
environment-variable was set.

Given the following docker compose file;

```yaml
version: "3.7"

services:
  app:
    image: nginx:${version:-latest}
```

Deploying a stack with `$version` set would ignore the `$version`
environment variable, and use the default value instead;

```bash
version=alpine docker stack deploy -c docker-compose.yml foobar

Creating network foobar_default
Creating service foobar_app

docker service ls

ID                  NAME                MODE                REPLICAS            IMAGE               PORTS
rskkjxe6sm0w        foobar_app          replicated          1/1                 nginx:latest
```

This patch also fixes "soft default" not detecting empty environment variables,
only non-set environment variables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit ec3daea021)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-26 15:20:15 +02:00
a31b20d7db fix subscription filter
Signed-off-by: Mason Fish <mason.fish@docker.com>
2018-09-24 16:56:05 -07:00
5ba5678898 Merge pull request #1386 from tiborvass/18.09-df-verbose-format-raw
[18.09] system/df: allow -v with --format
2018-09-22 00:25:21 -07:00
9de1318e36 system/df: allow -v with --format
This allows to provide more information for build cache disk usage.

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit a90b99edfc)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-09-22 01:33:03 +00:00
19e1ab273e Merge pull request #1381 from dhiltgen/revamp_18.09
[18.09] `docker engine` updates
2018-09-21 15:12:54 -07:00
ec1812188f Fix vendoring glitch
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-21 13:16:34 -07:00
6004d74b1f Fix lint glitches
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-21 11:01:17 -07:00
e79e591ee9 Merge pull request #1382 from thaJeztah/18.09_manifest-inspect-insecure-fix
[18.09] backport fix insecure manifest inspect with restrictive certs perms
2018-09-21 12:53:24 +02:00
0f22d7e295 Remove metadata file before writing
The packages will deliver this as a link so lets make sure we don't
write through the link to the underlying packaged file.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-20 18:00:46 -07:00
f250152bf4 Review comments
Address code review comemnts and purge additional dead code.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-20 12:01:20 -07:00
f9d666b057 fix insecure manifest inspect with restrictive certs perms
If, for some reason, the certs directory has permissions that are
inaccessible by docker, we should still be able to fetch manifests using
the `insecure` flag.

Since the cli doesn't access the engine's list of insecure registries,
the registry client should make a singleton list of the registry being queried with the
`insecure` flag.

Closes #1358

Signed-off-by: Christy Norman <christy@linux.vnet.ibm.com>
(cherry picked from commit d57adbc034)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-20 19:53:10 +02:00
342afe44fb Refined engine implementations
Adapt the CLI to the host install model for 18.09.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-19 20:10:31 -07:00
cfec8027ed Install binaries on host for upgrade
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2018-09-19 19:06:28 -07:00
78c42cf031 Merge pull request #1375 from thaJeztah/18.09_backport_importlcow
[18.09] backport LCOW: --platform on import (already in API)
2018-09-14 15:06:21 +02:00
dd2f13bed4 LCOW: --platform on import (already in API)
Signed-off-by: John Howard <jhoward@microsoft.com>
(cherry picked from commit b55a0b681f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-14 14:26:39 +02:00
3b991ec615 Merge pull request #1373 from thaJeztah/18.09_backport_move_test_function_in_there
[18.09] backport getEngineConfigFilePath is only used during test so moving it in test files for now.
2018-09-14 13:20:12 +02:00
34ea8bb5a5 Merge pull request #1374 from thaJeztah/18.09_backport_docs_fixes
[18.09] backport update usage for 'docker build' with '--progress' and '--secret' options
2018-09-14 12:55:50 +02:00
afb17ec70b update usage for 'docker build' with '--progress' and '--secret' options
Signed-off-by: Anda Xu <anda.xu@docker.com>
(cherry picked from commit 83ca55db7d)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-14 12:35:22 +02:00
62aed95bc1 getEngineConfigFilePath is only used during test…
… so moving it in test files for now.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
(cherry picked from commit 37ca5d6813)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-14 12:27:13 +02:00
649e4916bb Merge pull request #1368 from thaJeztah/carry-1360-store_prefix
[18.09] Prefix engine repo with store
2018-09-13 08:24:45 +02:00
3597d75281 Prefix engine repo with store
The official access point for the Q3 engine images will
be prefixed by store.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-12 13:43:04 +02:00
5673816fec Merge pull request #1364 from thaJeztah/18.09-backport_contains-containerized
[18.09] backport: remove containerizedengine package dependency from docker/cli/command…
2018-09-12 08:55:12 +02:00
a8c69c8287 Merge pull request #1365 from thaJeztah/18.09-backport_engine-only-linux
[18.09] backport add `docker engine` commands only on Linux…
2018-09-11 18:04:57 +02:00
fc3dc8f058 Remove containerizedengine package dependency from docker/cli/command…
… this removes a whole lot of dependencies from people depending on docker/cli…

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
(cherry picked from commit 2d344b2f61)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-11 17:01:02 +02:00
2a46a3d46c Add docker engine commands only on Linux…
… this is, for now, the only platform that is supported

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
(cherry picked from commit a3a955f204)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-09-11 16:57:54 +02:00
b2cf18ac2e connhelper: fix cmd.Wait() race
Fix #1336

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
(cherry picked from commit a22853e64d)
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
2018-09-05 16:03:41 -07:00
44371c7c34 Merge pull request #1339 from andrewhsu/vr
[18.09] vndr docker/docker to docker/engine d2ecc7b
2018-09-05 11:43:09 -07:00
4e6798794d vndr docker/docker to docker/engine d2ecc7b
And update the associated packages that have also updated from
docker/docker vendor.conf.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-09-05 17:35:51 +00:00
d8aefad94a Merge pull request #1334 from tiborvass/18.09-cmd-builder-prune-with-options
[18.09] build: add options to builder prune
2018-09-04 22:35:23 -07:00
3c37d6a034 system df: show table output for build cache
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit ca608c2302)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-09-05 03:33:30 +00:00
9d43f1ed48 build: add options to builder prune
This patch adds --filter, --keep-storage, --all and --force to builder prune.

Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit c806eb49c9)
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-09-05 03:33:30 +00:00
a818677813 Merge pull request #1322 from thaJeztah/18.09_backport_bump_kube_deps
[18.09] backport: bump kube dependency to 1.11.2
2018-08-29 17:56:26 +02:00
c204959687 Merge pull request #1323 from thaJeztah/18.09_backport_bump_golang_1.10.4
[18.09] backport: bump Go to 1.10.4
2018-08-29 15:32:30 +02:00
76c09259db Bump Go to 1.10.4
Includes fixes to the go command, linker, and the net/http, mime/multipart,
ld/macho, bytes, and strings packages. See the Go 1.10.4 milestone on the
issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.10.4

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 44ca0901d1)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-29 15:19:34 +02:00
0efb62cab1 Bump kube dependency to 1.11.2
Bump our kube dependencies to the latest patch
level for kube 1.11.

Signed-off-by: Marcus Martins <marcus@docker.com>
(cherry picked from commit c67e05796b)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-29 14:59:20 +02:00
8789e93d6e Merge pull request #1313 from dhiltgen/product_license
[18.09] Expose product license in info output
2018-08-28 21:05:09 -07:00
0ee05a6353 Merge pull request #1312 from dhiltgen/dual_keys
[18.09] Add support for multiple signing keys
2018-08-28 20:51:22 -07:00
68be7cb376 Expose product license in info output
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-28 20:10:09 -07:00
de805da04c Merge pull request #1317 from dhiltgen/play_nice_with_proxy
Update proxy config if present
2018-08-28 15:01:56 -07:00
b75350de7a Merge pull request #1311 from dhiltgen/fix_progress
[18.09] Fix progress reporting for containerd pulls
2018-08-28 14:59:59 -07:00
f96ddaedf7 Update proxy config if present
If the system has a containerd-proxy managing the lifecycle
of the daemon, make sure the config is updated with the new image
on update.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-28 10:11:44 -07:00
0fb6bb35a4 Add support for multiple signing keys
Basic enterprise licenses and platform license keys will be signed with
two different keys in the upcoming release.  This adds support for the
CLI to support both variants.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-27 15:20:30 -07:00
264ee43c2a Vendor bump for licensing library to support multiple signing keys
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-27 15:20:19 -07:00
7f4c842e8a Fix progress reporting for containerd pulls
During refactoring of the implementation PR progress reporting
was broken.  This gets the progress reporting back in action.

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-27 14:44:13 -07:00
e25e9d68be Merge pull request #1310 from thaJeztah/18.09-backport_update-docs
[18.09] backport: update docs with the new features option in daemon.json
2018-08-27 13:46:15 +02:00
6877dedeee update docs with the new features option in daemon.json
Signed-off-by: Anda Xu <anda.xu@docker.com>
(cherry picked from commit 3e0b0a6692)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-27 11:51:39 +02:00
08cf36daa6 Merge pull request #1303 from thaJeztah/remove_buildkit_experimental_annotations
Remove "experimental" annotations for buildkit
2018-08-22 00:57:27 +01:00
a500c394df Move "session" support out of experimental for API 1.39 and up
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-22 00:51:19 +02:00
60c75fda67 Remove "experimental" annotations for buildkit
BuildKit can now be enabled without the daemon having
experimental features enabled.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-22 00:38:59 +02:00
ca782599fb Merge pull request #1225 from thaJeztah/told_you_so_I_wont_warn_you_again
Use warnings provided by daemon
2018-08-21 13:42:16 -07:00
3c27ce21c9 Use warnings provided by daemon
Warnings are now generated by the daemon, and returned as
part of the /info API response.

If warnings are returned by the daemon; use those instead
of generating them locally.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-21 22:29:57 +02:00
7da71329bc bump docker/docker
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-08-21 22:29:43 +02:00
e97d004395 Merge pull request #1233 from selansen/default_addr_pool
Global Default Address Pool feature support
2018-08-21 14:55:24 -04:00
587a94c935 Global Default Address Pool feature support
This feature brings new attribute/option for swarm init command.
default-addr-pool will take string input which can be in below format.
"CIDR,CIDR,CIDR...:SUBNET-SIZE".
Signed-off-by: selansen <elango.siva@docker.com>
2018-08-21 14:34:00 -04:00
2461cd618d Merge pull request #1275 from AntaresS/buildkit-support
[enhancement] enable buildkit from daemon side
2018-08-20 16:15:32 -07:00
acf43b62b5 vendor dependency
Signed-off-by: Anda Xu <anda.xu@docker.com>
2018-08-20 15:32:25 -07:00
ef09ca8987 enable buildkit as builder from daemon; no env var needs to be set
Signed-off-by: Anda Xu <anda.xu@docker.com>
2018-08-20 11:59:39 -07:00
466e1b0741 Merge pull request #1302 from andrewhsu/venbk
vndr buildkit, containerd, console
2018-08-20 11:10:46 -07:00
7a73d112ff vndr buildkit, containerd, and console
vndr buildkit to e8c7acc
vndr containerd to v1.2.0-beta.0
vndr console to 4d8a41f

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-08-20 17:59:40 +00:00
0c444c521f Merge pull request #1260 from dhiltgen/ce_q3
Add CLI support for running dockerd in a container on containerd
2018-08-20 13:39:49 -04:00
fd2f1b3b66 Add engine commands built on containerd
This new collection of commands supports initializing a local
engine using containerd, updating that engine, and activating
the EE product

Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-20 09:42:05 -07:00
11a312118f Vendoring bump for containerd and licensing
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-20 09:42:05 -07:00
3f7c6c8200 Merge pull request #1300 from mmacy/1299-trust-inspect-typo
Fix 'trust inspect' typo: "AdminstrativeKeys"
2018-08-20 10:08:45 +02:00
c11acddfb5 [WIP] fix trust inspect typo 'AdminstrativeKeys'
Signed-off-by: mmacy <marsma@microsoft.com>
2018-08-19 17:57:04 -07:00
5706f9518a Merge pull request #1297 from tiborvass/tls-update
vendor: update tlsconfig in go-connections to 7395e3f8aa162843a74ed6d48e79627d9792ac55
2018-08-19 14:46:46 -07:00
f472a1a480 Merge pull request #1296 from tiborvass/build-progress-flag-no-api-requirement
build: Remove API requirement for --progress as it is CLI only
2018-08-17 18:31:25 -07:00
b3d8c5deda Merge pull request #1295 from tiborvass/cmd-builder-prune-no-options
builder: Implement `builder prune` to prune build cache
2018-08-17 18:28:37 -07:00
8ae74b38d5 vendor: update tlsconfig in go-connections to 7395e3f8aa162843a74ed6d48e79627d9792ac55
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-17 17:53:22 +00:00
50f918801f build: Remove API requirement for --progress as it is CLI only
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-17 16:24:02 +00:00
cb142fa49f Merge pull request #1288 from tiborvass/build-secrets
Build --secret with buildkit
2018-08-17 17:20:32 +01:00
b4057f0293 vendor: Bump default API version to 1.39
vendors github.com/docker/docker to a7ff19d69a90dfe152abd146221c8b9b46a0903d

Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-17 15:52:11 +00:00
f597f2d026 Add new builder subcommand and implement builder prune to prune build cache.
This patch adds a new builder subcommand, allowing to add more builder-related
commands in the future. Unfortunately `build` expects an argument so could not
be used as a subcommand.

This also implements `docker builder prune`, which is needed to prune the builder
cache manually without having to call `docker system prune`.

Today when relying on the legacy builder, users are able to prune dangling images
(used as build cache) by running `docker image prune`. This patch allows the
same usecase with buildkit.

Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-17 15:18:18 +00:00
c4c4825591 build: implement build secrets with buildkit
This patch implements `docker build --secret id=mysecret,src=/secret/file`
for buildkit frontends that request the mysecret secret.

It is currently implemented in the tonistiigi/dockerfile:secrets20180808
frontend via RUN --mount=type=secret,id=mysecret

Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-17 14:01:32 +00:00
964173997d Merge pull request #1276 from tiborvass/buildkit-progress-flag
build: change --console=[auto,false,true] to --progress=[auto,plain,tty]
2018-08-15 20:42:47 -07:00
e92614a175 Merge pull request #1014 from AkihiroSuda/connhelper-sshonly
support SSH connection
2018-08-14 15:12:06 -07:00
4d4392ba04 Merge pull request #1284 from adshmh/refactor-stack-ps-tests
refactor stack ps tests
2018-08-14 18:59:56 +02:00
340e4ee8e5 refactor stack ps tests to table-driven
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-10 15:13:45 -04:00
3a3e720f91 Merge pull request #1283 from adshmh/refactor-trust-revoke-tests
refactor trust revoke command unit tests
2018-08-10 11:03:58 +02:00
560b0cd863 Merge pull request #1280 from adshmh/refactor-trust-inspect-tests
refactor trust inspect command unit tests
2018-08-10 10:37:53 +02:00
984d76b9dd refactored trust revoke command unit tests to use table-driven style
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-09 15:58:54 -04:00
6ef11c516d Merge pull request #1266 from adshmh/use-natural-sort-order-for-network-list
use sortorder library for sorting network list output
2018-08-09 12:05:55 +02:00
f8f0d72cf9 refactor trust inspect command unit tests to table-driven style
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-08 13:50:00 -04:00
5cc1f9006a use sortorder lib for sorting the output of volume list command
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-08 12:20:21 -04:00
04bb3c770f use sortorder lib for sorting in trust package
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-08 12:20:10 -04:00
a921313caf use sortorder lib for sorting network list output
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-08 12:06:59 -04:00
faeb8bb571 build: change --console=[auto,false,true] to --progress=[auto,plain,tty]
This changes the experimental --console flag to --progress following
feedback indicating avoidable confusion.

In addition to naming changes, the help output now has an additional
clarification, specifically: container output during builds are only
shown when progress output is set to plain. Not mentioning this was also
a big cause of confusion.

Signed-off-by: Tibor Vass <tibor@docker.com>
2018-08-07 18:18:13 +00:00
1d04f7d66b Merge pull request #1277 from vdemeester/template-subtests-followup
Migrate `TestExtractVariables` to subtests…
2018-08-07 16:30:11 +02:00
9cd7c1361c Migrate TestExtractVariables to subtests…
… as suggested in previous PR comment.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-08-07 16:06:13 +02:00
b5768bea9b Merge pull request #1271 from albers/completion-shellcheck
Fix shellcheck warnings
2018-08-04 01:36:36 +02:00
e8cd06c8eb Merge pull request #1269 from albers/completion-cp--archive
Add bash completion for `cp --archive`
2018-08-03 23:48:52 +02:00
ad28cec012 Merge pull request #1270 from Ace-Tang/master
bash completion: fix uncorrect completion
2018-08-03 23:26:09 +02:00
e587ec293b Fix shellcheck warnings
Signed-off-by: Harald Albers <github@albersweb.de>
2018-08-03 15:28:09 +02:00
7b4e2f3145 bash completion: fix uncorrect completion
fix uncorrect completion for command
docker docker <tab>

Signed-off-by: Ace-Tang <aceapril@126.com>
2018-08-03 21:27:27 +08:00
b9b3754ad3 Add bash completion for cp --archive
Signed-off-by: Harald Albers <github@albersweb.de>
2018-08-03 14:30:30 +02:00
e902ae9f84 Merge pull request #1259 from adshmh/add-unit-test-to-cover-network-list-sort
Add unit test to cover network list sort, refactor to table-driven tests
2018-08-02 20:28:53 +02:00
021bf39d76 refactor network list unit tests to table-driven style
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-02 14:07:06 -04:00
4f388ffca3 add unit test to cover the sort order of network list command
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-08-02 14:07:06 -04:00
ff1a34d9a9 Merge pull request #1262 from vdemeester/allow-custom-pattern
Allow custom pattern when extracting variable…
2018-08-02 18:00:38 +02:00
731b4f1fb4 Merge pull request #1254 from albers/completion-kubernetes
Add bash completion for kubernetes orchestrator
2018-08-02 17:30:31 +02:00
4c87725c35 Allow custom pattern when extracting variable…
… as it is possible to do it when interpolating. It also fixes when
there is 2 variables on the same *value* (in the composefile, on the
same line)

Finaly, renaming the default, used in cli, pattern to `defaultPattern`
to not be shadowed unintentionally.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-08-02 16:50:40 +02:00
08f8ee1320 Add bash completion for kubernetes orchestrator
Signed-off-by: Harald Albers <github@albersweb.de>
2018-08-02 13:54:31 +02:00
8ef01e869e Only complete swarm specific options with orchestrator=swarm
Signed-off-by: Harald Albers <github@albersweb.de>
2018-08-02 13:54:31 +02:00
ff953751d3 Add support for orchestrator specific bash completions
Signed-off-by: Harald Albers <github@albersweb.de>
2018-08-02 13:54:31 +02:00
4fbb009d39 Merge pull request #1251 from silvin-lubecki/fix-stack-help-command
Fix help message flags on docker stack commands and sub-commands
2018-08-02 13:10:36 +02:00
6f61cf053a support SSH connection
e.g. docker -H ssh://me@server

The `docker` CLI also needs to be installed on the remote host to
provide `docker system dial-stdio`, which proxies the daemon socket to stdio.

Please refer to docs/reference/commandline/dockerd.md .

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
2018-08-02 13:10:06 +09:00
261ff66d61 Merge pull request #1258 from wyckster/patch-2
Fixed wrong apostrophe character
2018-08-01 18:09:36 +02:00
b59823c784 Fixed wrong apostrophe character
Changed unexpected Unicode character 0x1fbf GREEK PSILI that was standing in as an imposter for an apostrophe:  an _impostrophe_.

Signed-off-by: Chad Faragher <wyckster@hotmail.com>
2018-08-01 11:06:07 -04:00
da544e8938 Merge pull request #1249 from vdemeester/compose-template-pkg-enhancement
Add a new `ExtractVariables` function to `compose/template` package
2018-08-01 16:18:46 +02:00
afb87e42f2 Add a new ExtractVariables function to compose/template package
It allows to get easily all the variables defined in a
composefile (the `map[string]interface{}` representation that
`loader.ParseYAML` returns at least) and their default value too.

This commit also does some small function extract on substitution
funcs to reduce a tiny bit duplication.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-08-01 16:12:49 +02:00
2093fd6e52 Merge pull request #1256 from silvin-lubecki/update-reviewers
Remove outdated completion reviewers file
2018-08-01 15:19:36 +02:00
9022a00fbe Remove outdated completion reviewers file
Clean maintainers and code owners files

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2018-08-01 15:08:36 +02:00
21cce52b30 Fix help message flags on docker stack commands and sub-commands
PersistentPreRunE needs to be called within the help function to initialize all the flags (notably the orchestrator flag)
Add an e2e test as regression test

Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2018-08-01 01:48:27 +02:00
08f5f52cdc Merge pull request #1215 from albers/completion-service-logs-options
Add bash completion for `service logs --details|--raw`
2018-07-31 22:25:36 +02:00
24b7effa30 Merge pull request #1246 from vdemeester/bump-mergo
Bump mergo to v0.3.6
2018-07-31 10:59:41 -07:00
74071f2347 Merge pull request #1248 from marcov/add-help-target
Add `help` and remove `watch` targets in docker.Makefile
2018-07-31 16:52:07 +02:00
760ca04709 Add help and remove watch targets in docker.Makefile
* Add the `help` target to document make targets when building using a
container
* Remove the `watch` target (filewatcher was removed with c0588a9c) from
docker.Makefile and Makefile

Signed-off-by: Marco Vedovati <mvedovati@suse.com>
2018-07-31 14:38:51 +02:00
7f853fee87 Merge pull request #1247 from marcov/make-help
Allow running `make help` without out-of-container warning
2018-07-31 14:04:10 +02:00
265dec037b Allow running make help without out-of-container warning
Signed-off-by: Marco Vedovati <mvedovati@suse.com>
2018-07-31 10:56:48 +02:00
40650cfbd5 Merge pull request #1242 from cyphar/buildmode-pie
build: add -buildmode=pie
2018-07-31 10:09:11 +02:00
1f1507b0a4 Bump mergo to v0.3.6
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-31 09:57:56 +02:00
70d5cb0dd0 Merge pull request #1244 from vdemeester/expose-compose-transform
Exposes compose `loader.Transform` function…
2018-07-31 09:43:13 +02:00
0246bc1b3b Exposes compose loader.Transform function…
This should make it easier for people to write custom composefile
parser without duplicating too much code. It takes the default
transformers and any additional number of transformer for any
types. That way it's possible to transform a `cli/compose` map into a
custom type that would use some of `cli/compose` types and its own.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-31 09:37:09 +02:00
b4e50635a2 Merge pull request #1230 from vdemeester/update-k8s-and-other-deps
Update k8s and other deps
2018-07-30 17:49:11 +02:00
19653e7fad Merge pull request #1240 from thaJeztah/datapath_addr_api_annotation
Add API-version anotation for --data-path-addr
2018-07-30 17:47:52 +02:00
6cd0e2fe70 Merge pull request #1222 from thaJeztah/its_not_standard_but_its_in
Update --compose-file flag description to mention stdin
2018-07-30 16:23:29 +02:00
fffec04374 Re-order vendor.conf alphabeticaly
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-30 16:14:52 +02:00
ed335aba8c Merge pull request #990 from vdemeester/e2e-iddfile-from-moby
Import TestBuildIidFileSquash from moby to cli
2018-07-30 11:37:36 +02:00
164e812b7a build: add -buildmode=pie
Make all dynbinary builds be position-independent (this adds both
security benefits and can help with flaky builds on POWER
architectures).

Signed-off-by: Aleksa Sarai <asarai@suse.de>
2018-07-30 19:34:01 +10:00
91b1ad9d2b Add API-version anotation for --data-path-addr
This flag was added in Docker 17.06, API version 1.31 through
moby@8dc8cd4719f165c01c98e7d3ce1d6cea6a8f60b8, but didn't add
API-version annotations.

This patch adds the missing annotations to hide this flag if
the CLI is connected to an older version of the daemon that
doesn't support that API.

Before this patch:

    DOCKER_API_VERSION=1.30 docker swarm init --help | grep data-path-addr
          --data-path-addr string           Address or interface to use for data path traffic (format: <ip|interface>)

    DOCKER_API_VERSION=1.31 docker swarm init --help | grep data-path-addr
          --data-path-addr string           Address or interface to use for data path traffic (format: <ip|interface>)

With this patch applied:

    DOCKER_API_VERSION=1.30 docker swarm init --help | grep data-path-addr
    # (no result)

    DOCKER_API_VERSION=1.31 docker swarm init --help | grep data-path-addr
          --data-path-addr string           Address or interface to use for data path traffic (format: <ip|interface>)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-30 11:05:20 +02:00
edfd623594 Merge pull request #1237 from vdemeester/use-non-deprecated-filters-funcs
Migrate to non-deprecated functions of `api/types/filters`
2018-07-27 14:47:01 +00:00
55edeb497a Migrate to non-deprecated functions of api/types/filters
- Use `Contains` instead of `Include`
- Use `ToJSON` instead of `ToParam`
- Remove usage of `ParseFlag` as it is deprecated too

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-27 15:37:43 +02:00
a3464c0a20 Merge pull request #1140 from Vimal-Raghubir/1110-Fix-Warning-Message
Fix: Warning Message and Fallback search
2018-07-27 07:27:00 +00:00
b3b2ace735 Merge pull request #1235 from vdemeester/compose-add-missing-field
Add missing fields in compose/types
2018-07-26 15:20:42 +00:00
d13e2df65b Add missing fields in compose/types
Even though those fields are not supported by `docker stack deploy`
they are defined in versions `3.x` of compose schema, so the `compose`
package should be able to marshal/unmarshal them.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-26 17:04:22 +02:00
effe36a155 Merge pull request #1232 from tommilligan/1231-zsh-autocomplete-update-invalid
Fixed typo breaking zsh docker update autocomplete (closes #1231)
2018-07-25 14:17:57 +02:00
8788a4804f Bump some dependencies to more recent versions (and tagged if available)
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-25 14:16:41 +02:00
da00d1c49f Fixed typo breaking zsh docker update autocomplete
Signed-off-by: Tom Milligan <tommilligan@users.noreply.github.com>
2018-07-25 12:23:08 +01:00
c8f0e211b9 Bump kubernetes dependencies to 1.11
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-25 11:34:59 +02:00
601131634e Merge pull request #1227 from pszczekutowicz/master
Added events filter scope for bash and zsh completion
2018-07-22 22:33:25 +02:00
13db1bc95f Added events filter scope zsh completion
Signed-off-by: Paweł Szczekutowicz <pszczekutowicz@gmail.com>
2018-07-20 15:47:00 +02:00
c922ea2f45 Added events filter scope bash completion
Signed-off-by: Paweł Szczekutowicz <pszczekutowicz@gmail.com>
2018-07-20 15:37:49 +02:00
543d6fb8da Merge pull request #1226 from thaJeztah/harder_better_slower_stronger
Bump version to 18.09.0-dev
2018-07-20 10:30:41 +02:00
7fba38acad Bump version to 18.09.0-dev
Releases will be supported for a longer term, and
will be done on a 6-month(ish) cycle.

https://blog.docker.com/2018/07/extending-support-cycle-docker-community-edition/

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-20 10:14:32 +02:00
2c7822b036 Update --compose-file flag description to mention stdin
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-19 14:55:48 +02:00
48fbb12b7c Merge pull request #1216 from justyntemme/patch-4
Update deploy.go
2018-07-17 16:20:46 +02:00
bdd58a4096 Update deploy.go
Clarified ambiguous error message

Update kubernetes/cli.go

Infromed user of why the error was caused when file is not there

Signed-off-by: Justyn Temme <justyntemme@gmail.com>
2018-07-17 09:14:05 -05:00
4912846de2 Add bash completion for service logs --details|--raw
Signed-off-by: Harald Albers <github@albersweb.de>
2018-07-16 18:46:06 +02:00
9e71207327 Merge pull request #1019 from ktomk/fix-env-file
import environment variables that are present
2018-07-16 13:41:29 +02:00
7b82276c88 Merge pull request #1192 from peter-kehl/master
For docker/docker.github.io/issues/6987
2018-07-16 12:22:02 +02:00
b395d2d6f5 Merge pull request #1212 from thaJeztah/bump_circleci_docker
Update CircleCI Docker version to 18.03
2018-07-13 15:13:40 +02:00
ce3d069936 Fix: Warning Message
Signed-off-by: Vimal-Raghubir <vraghubir0418@gmail.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-13 13:39:24 +02:00
13eb2aa125 Configure CircleCI remote daemon to use version 18.03.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-13 11:51:41 +02:00
d57cc1782e Merge pull request #1208 from thaJeztah/update-linting
Update gometalinter to v2.0.6, remove nakedret
2018-07-13 11:41:59 +02:00
e2a56c47da Update CircleCI Docker version to 18.03
17.06 has reached EOL a long time ago; let's use a current
version in CI as well :D

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-13 11:26:07 +02:00
9847e96765 Update hints for linting
- remove some hints that are no longer needed
- added a nolint: unparam for removeSingleSigner() (return bool is only used in tests)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-13 11:18:50 +02:00
f3811e865e Update gometalinter to v2.0.6 and remove alexkohler/nakedret
alexkohler/nakedret is now installed by default with gometalinter,
so it's no longer needed to install this manually

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-13 11:17:43 +02:00
d97f378009 Merge pull request #1210 from albers/completion-service-create--init
Add bash completion for `service create|update --init`
2018-07-13 09:44:11 +02:00
97d312e02a Add bash completion for service create|update --init
Signed-off-by: Harald Albers <github@albersweb.de>
2018-07-13 09:05:29 +02:00
ee8cdb3850 Merge pull request #1204 from thaJeztah/improve-version-align
Improve version output alignment
2018-07-12 10:53:50 +02:00
0f7ae34ea9 Adapt min-column width to component information
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-12 01:34:34 +02:00
55ff66d967 Extend version-align test with components
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-12 01:31:23 +02:00
c8b9c21ef9 Merge pull request #1178 from cyli/fix-swarm-ca-command
Propagate the provided external CA certificate to the external CA object in swarm
2018-07-10 01:31:32 +02:00
b91953f507 Merge pull request #1196 from adshmh/use-sort-slice-for-sorting-cli-compose
refactored commands to use sort.Slice
2018-07-09 07:18:11 -07:00
71d650ee17 refactored cli/compose and cli/command/trust to use sort.Slice and removed custom types used for sorting
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-07-08 15:08:17 -04:00
2b221d8f1c Merge pull request #1194 from adshmh/use-sort-slice-for-sorting-output
use sort.Slice for sorting commands' output
2018-07-07 18:13:12 +02:00
ceed42217d refactored all commands under cli/command/ to use sort.Slice instead of declaring custom types for sorting
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-07-06 15:49:32 -04:00
2634562119 Merge pull request #1102 from sfluor/1074-fix-mapping-a-range-of-host-ports-to-a-single-container-port
Fix mapping a range of host ports to a single container port
2018-07-06 14:41:00 +02:00
8160759013 Merge pull request #1166 from adshmh/add-sort-to-plugin-list
Sort plugin names in a natural order
2018-07-05 16:34:25 -07:00
249c8652a2 For docker/docker.github.io/issues/6987
Signed-off-by: Peter Kehl <peter.kehl@gmail.com>
2018-07-05 14:12:47 -07:00
26151d910a The output of plugin list command is sorted by plugin name
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-07-05 12:35:53 -04:00
29612ccefe Adding support of the long syntax publish notation
Signed-off-by: Sami Tabet <salph.tabet@gmail.com>
2018-07-05 00:33:13 +02:00
f285fe67e9 Merge pull request #1163 from thaJeztah/bump_engine
bump docker and dependencies
2018-07-04 16:17:12 +02:00
5f6d5c7328 Bump docker and dependencies
Updates docker/docker to 1436dc8f8d0f6f60b6e335fbd918d6b22ee6574d,
matching 18.06.0-rc1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-07-04 01:14:40 +00:00
bded5beb78 Merge pull request #1186 from tiborvass/buildkit-envvar-zero
build: use strconv.ParseBool to parse DOCKER_BUILDKIT to allow value "0"
2018-07-04 01:28:03 +02:00
721000e6c9 build: use strconv.ParseBool to parse DOCKER_BUILDKIT to allow value "0"
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-07-03 23:14:06 +00:00
7b255e653a Merge pull request #1176 from tiborvass/buildkit-iidfile
build: --iidfile support with buildkit
2018-07-03 15:36:23 -07:00
c7e85c09d2 build: --iidfile support with buildkit
Signed-off-by: Tibor Vass <tibor@docker.com>
2018-07-03 19:11:11 +00:00
69e1743e3d Merge pull request #1156 from dmcgowan/fix-manifest-list-size
Fix manifest lists to always use correct size
2018-07-03 11:27:19 -07:00
4243440e1f Propagate the provided external CA certificate to the external CA object
in swarm.

Also, fix some CLI command confusions:
1. If the --external-ca flag is provided, require a --ca-cert flag as well, otherwise
   the external CA is set but the CA certificate is actually rotated to an internal
   cert
2. If a --ca-cert flag is provided, require a --ca-key or --external-ca flag be
   provided as well, otherwise either the server will say that the request is
   invalid, or if there was previously an external CA corresponding to the cert, it
   will succeed.  While that works, it's better to require the user to explicitly
   set all the parameters of the new desired root CA.

This also changes the `swarm update` function to set the external CA's CACert field,
which while not strictly necessary, makes the CA list more explicit.

Signed-off-by: Ying Li <ying.li@docker.com>
2018-07-02 17:14:21 -07:00
f5393c904a Merge pull request #1175 from vdemeester/bump-k8s
Bump kubernetes dependencies to 1.8.14
2018-07-02 17:12:35 +02:00
b59c41b2a7 Bump kubernetes dependencies to 1.8.14
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-02 16:32:07 +02:00
95a9b4d5fe Merge pull request #1172 from vdemeester/no-need-to-check-files
Remove composefiles length check on k8s RunDeploy
2018-07-02 15:06:50 +02:00
847e0c22d4 Remove composefiles lenght check on k8s RunDeploy..
The compose file(s) are already loaded at that point.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-02 15:00:19 +02:00
a2b4d30cd0 Merge pull request #1171 from silvin-lubecki/fix-doc-typo
Fix Format example typo"
2018-07-02 14:47:39 +02:00
d0ddf91539 Fixing issue #1167 "Format example typo"
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2018-07-02 14:21:10 +02:00
18091ea7e2 Merge pull request #1170 from vdemeester/omit-silvin
Add omitempty on compose config top-level types
2018-07-02 14:16:53 +02:00
f05ab2b1fb Add omitempty on compose config top-level types
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-02 12:04:09 +02:00
981c099b96 Merge pull request #1169 from silvin-lubecki/schema-vendoring
Add a doc.go file so the compose/schema/data directory can be vendore…
2018-07-02 10:47:25 +02:00
1c69e83034 Merge pull request #1168 from vdemeester/update-testing
Update TESTING.md to replace testify by gotest.tools
2018-07-02 10:34:28 +02:00
3a8ef767f8 Add a doc.go file so the compose/schema/data directory can be vendored in another project, without being pruned.
Signed-off-by: Silvin Lubecki <silvin.lubecki@docker.com>
2018-07-02 10:08:25 +02:00
9e36ff4491 Merge pull request #1160 from euank/simpler-pass
config/credentials: don't run 'pass' to detect it
2018-07-02 09:40:47 +02:00
057bf6f4d1 Update TESTING.md to replace testify by gotest.tools
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-07-02 09:24:28 +02:00
b91fd12996 add test for zero length variable name
parsing an environment file should give an error in case a zero-length
variable name (definition w/o a variable name) is encountered.

previously these lines went through unnoticed not informing the user about
a potential configuration error.

Signed-off-by: Tom Klingenberg <tklingenberg@lastflood.net>
2018-07-02 07:52:02 +02:00
96c026eb30 import environment variables that are present
previously docker did import environment variables if they were present
but created them if they were not when it was asked via a --env-file
cli option to import but not create them.

fix is to only import the variable into the environment if it is present.

additionally do not import variable names of zero-length (which are lines
w/ a potential variable definition w/o a variable name).

refs:

- https://github.com/docker/for-linux/issues/284

Signed-off-by: Tom Klingenberg <tklingenberg@lastflood.net>
2018-07-02 07:37:12 +02:00
1e89745704 add test for undefined variable environment file import
test to show current behavior is wrong at parsing an environment file
defining an undefined variable - it must not be defined!

NOTE: this test assume the $HOME variable is always set (see POSIX, this
      normally is the case, e.g. the test suite remains stable).

Signed-off-by: Tom Klingenberg <tklingenberg@lastflood.net>
2018-07-02 07:33:44 +02:00
34ba66b0c5 Merge pull request #1157 from AzureCR/master
Updated the go-winio library to release 0.4.8 that has the fix for Windows Container
2018-06-29 21:11:53 +02:00
056015c3d8 config/credentials: don't run 'pass' to detect it
'CheckInitialized' in the credential-helper library actually invokes
`pass`, which isn't desirable (see #699).

This moves the check to be simpler, and then pass will only be invoked
when it's needed (such as for `docker login` or when pulling from a
private registry).

This logic could also reasonably live in the credential-helper library,
but it's simple enough it seems fine in either location.

Signed-off-by: Euan Kemp <euank@euank.com>
2018-06-29 11:38:39 -07:00
c98c4080a3 Updated the go-winio library to release 0.4.8 that has the fix for Windows containers
Signed-off-by: Tejaswini Duggaraju <naduggar@microsoft.com>
2018-06-29 10:49:52 -07:00
da59ccb601 Merge pull request #1145 from Vimal-Raghubir/590-Add-missing-option
Add: Add missing option
2018-06-29 17:16:25 +02:00
9faf728089 Merge pull request #1155 from adshmh/add-unit-tests-to-plugin-list
added unit tests to cover plugin list command
2018-06-29 15:49:30 +02:00
7c7c299eee Merge pull request #1152 from vdemeester/extract-converter
Extract StackConverter from the StackClient
2018-06-29 13:36:55 +02:00
3991b2fae3 Merge pull request #1158 from albers/completion-dockerd--default-address-pool
Add bash completion for `dockerd --default-address-pool`
2018-06-29 11:48:43 +02:00
fe7ec42566 Merge pull request #1159 from albers/completion-events-exec_die
Add bash completion for `exec_die` event
2018-06-29 11:47:58 +02:00
8443982188 Add bash completion for exec_die event
Signed-off-by: Harald Albers <github@albersweb.de>
2018-06-29 11:20:23 +02:00
0e6d9dfe85 Add bash completion for dockerd --default-address-pool
Signed-off-by: Harald Albers <github@albersweb.de>
2018-06-29 11:11:24 +02:00
1fd2d66df8 Fix manifest lists to always use correct size
Stores complete OCI descriptor instead of digest and platform
fields. This includes the size which was getting lost by not
storing the original manifest bytes.

Attempt to support existing cached files, if not output
the filename with the incorrect content.

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
2018-06-28 18:17:38 -07:00
c26121df5c added unit tests to cover plugin list command
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-06-28 16:51:54 -04:00
ea65e9043c Merge pull request #1154 from thaJeztah/bump_version_18.07_dev
Bump version to 18.07.0-dev
2018-06-28 16:24:08 +02:00
f1fa1f3f15 Bump version to 18.07.0-dev
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2018-06-28 15:44:15 +02:00
293553944d Merge pull request #1151 from vdemeester/update-docker-credential-helper-pass
Update docker-credential-helpers dependency
2018-06-28 14:57:05 +02:00
d9741fc96b Update docker-credential-helpers dependency
This is mainly for the `pass` helper ; `pass` shouldn't be called
every docker command anymore ;).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-28 14:44:53 +02:00
b21f9dde61 Merge pull request #1149 from adshmh/add-unit-tests-to-plugin-install
added unit tests covering content trust for plugin install command
2018-06-28 09:28:18 +02:00
bc9b42ea9b added unit tests covering content trust for plugin install command
Signed-off-by: Arash Deshmeh <adeshmeh@ca.ibm.com>
2018-06-27 22:16:19 -04:00
f2e6ee6899 Extract StackConverter from the StackClient
It makes it easier to get the correct stack from a compose config
struct without requiring the client (and thus talking to k8s API)

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-27 16:41:00 +02:00
a205aecb80 Add: Add missing option
Signed-off-by: Vimal-Raghubir <vraghubir0418@gmail.com>
2018-06-26 22:20:01 -04:00
a522a78231 Make test-e2e run against experimental and non-experimental daemon
- `make test-e2e` runs the e2e tests twice : once against on
  non-experimental daemon (as before), once against an experimental
  daemon.
- adds `test-e2e-experimental` and `test-e2e-non-experimental` target
  to run tests for the specified cases

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-25 11:46:35 +02:00
0e83042e54 Import TestBuildIidFileSquash from moby to cli
It's a cli only feature so the test belongs to the cli.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
2018-06-25 11:44:09 +02:00
63e5c29e00 Fix mapping a range of host ports to a single container port
Signed-off-by: Sami Tabet <salph.tabet@gmail.com>
2018-06-12 11:54:02 +02:00
1871 changed files with 238009 additions and 68411 deletions

4
.github/CODEOWNERS vendored
View File

@ -1,8 +1,8 @@
# GitHub code owners
# See https://github.com/blog/2392-introducing-code-owners
cli/command/stack/** @vdemeester
cli/command/stack/** @vdemeester @silvin-lubecki
cli/compose/** @vdemeester
contrib/completion/bash/** @albers
contrib/completion/zsh/** @sdurrheimer
docs/** @mistyhacks @vdemeester @thaJeztah
docs/** @vdemeester @thaJeztah

View File

@ -41,7 +41,6 @@
# TODO Describe the docs maintainers role.
people = [
"misty",
"thajeztah"
]
@ -95,11 +94,6 @@
Email = "justin.cormack@docker.com"
GitHub = "justincormack"
[people.misty]
Name = "Misty Stanley-Jones"
Email = "misty@docker.com"
GitHub = "mistyhacks"
[people.programmerq]
Name = "Jeff Anderson"
Email = "jeff@docker.com"

View File

@ -46,10 +46,6 @@ binary-osx: ## build executable for macOS
dynbinary: ## build dynamically linked binary
./scripts/build/dynbinary
.PHONY: watch
watch: ## monitor file changes and run go test
./scripts/test/watch
vendor: vendor.conf ## check that vendor matches vendor.conf
rm -rf vendor
bash -c 'vndr |& grep -v -i clone'

View File

@ -26,13 +26,11 @@ Test<Function Name><Test Case Name>
where appropriate, but may not be appropriate in all cases.
Assertions should be made using
[testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and test
requirements should be verified using
[testify/require](https://godoc.org/github.com/stretchr/testify/require).
[gotest.tools/assert](https://godoc.org/gotest.tools/assert).
Fakes, and testing utilities can be found in
[internal/test](https://godoc.org/github.com/docker/cli/internal/test) and
[gotestyourself](https://godoc.org/github.com/gotestyourself/gotestyourself).
[gotest.tools](https://godoc.org/gotest.tools).
## End-to-End Test Suite

View File

@ -1 +1 @@
18.06.0-dev
18.09.0-dev

View File

@ -4,7 +4,7 @@ clone_folder: c:\gopath\src\github.com\docker\cli
environment:
GOPATH: c:\gopath
GOVERSION: 1.10.3
GOVERSION: 1.10.8
DEPVERSION: v0.4.1
install:

View File

@ -4,10 +4,11 @@ jobs:
lint:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker: [{image: 'docker:18.03-git'}]
steps:
- checkout
- setup_remote_docker:
version: 18.03.1-ce
reusable: true
exclusive: false
- run:
@ -22,11 +23,12 @@ jobs:
cross:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker: [{image: 'docker:18.03-git'}]
parallelism: 3
steps:
- checkout
- setup_remote_docker:
version: 18.03.1-ce
reusable: true
exclusive: false
- run:
@ -48,10 +50,11 @@ jobs:
test:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker: [{image: 'docker:18.03-git'}]
steps:
- checkout
- setup_remote_docker:
version: 18.03.1-ce
reusable: true
exclusive: false
- run:
@ -76,10 +79,11 @@ jobs:
validate:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker: [{image: 'docker:18.03-git'}]
steps:
- checkout
- setup_remote_docker:
version: 18.03.1-ce
reusable: true
exclusive: false
- run:
@ -93,10 +97,13 @@ jobs:
make ci-validate
shellcheck:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker: [{image: 'docker:18.03-git'}]
steps:
- checkout
- setup_remote_docker
- setup_remote_docker:
version: 18.03.1-ce
reusable: true
exclusive: false
- run:
name: "Run shellcheck"
command: |

View File

@ -0,0 +1,22 @@
package builder
import (
"github.com/spf13/cobra"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
)
// NewBuilderCommand returns a cobra command for `builder` subcommands
func NewBuilderCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "builder",
Short: "Manage builds",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
NewPruneCommand(dockerCli),
)
return cmd
}

View File

@ -0,0 +1,96 @@
package builder
import (
"context"
"fmt"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
type pruneOptions struct {
force bool
all bool
filter opts.FilterOpt
keepStorage opts.MemBytes
}
// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune",
Short: "Remove build cache",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil
},
Annotations: map[string]string{"version": "1.39"},
}
flags := cmd.Flags()
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'unused-for=24h')")
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
return cmd
}
const (
normalWarning = `WARNING! This will remove all dangling build cache. Are you sure you want to continue?`
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
)
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := options.filter.Value()
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
warning := normalWarning
if options.all {
warning = allCacheWarning
}
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return 0, "", nil
}
report, err := dockerCli.Client().BuildCachePrune(context.Background(), types.BuildCachePruneOptions{
All: options.all,
KeepStorage: options.keepStorage.Value(),
Filters: pruneFilters,
})
if err != nil {
return 0, "", err
}
if len(report.CachesDeleted) > 0 {
var sb strings.Builder
sb.WriteString("Deleted build cache objects:\n")
for _, id := range report.CachesDeleted {
sb.WriteString(id)
sb.WriteByte('\n')
}
output = sb.String()
}
return report.SpaceReclaimed, output, nil
}
// CachePrune executes a prune command for build cache
func CachePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
}

View File

@ -8,17 +8,20 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"time"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/config"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/connhelper"
cliflags "github.com/docker/cli/cli/flags"
manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/trust"
dopts "github.com/docker/cli/opts"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
@ -53,19 +56,21 @@ type Cli interface {
ManifestStore() manifeststore.Store
RegistryClient(bool) registryclient.RegistryClient
ContentTrustEnabled() bool
NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error)
}
// DockerCli is an instance the docker command line client.
// Instances of the client can be returned from NewDockerCli.
type DockerCli struct {
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
client client.APIClient
serverInfo ServerInfo
clientInfo ClientInfo
contentTrust bool
configFile *configfile.ConfigFile
in *InStream
out *OutStream
err io.Writer
client client.APIClient
serverInfo ServerInfo
clientInfo ClientInfo
contentTrust bool
newContainerizeClient func(string) (clitypes.ContainerizedClient, error)
}
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
@ -129,6 +134,20 @@ func (cli *DockerCli) ContentTrustEnabled() bool {
return cli.contentTrust
}
// BuildKitEnabled returns whether buildkit is enabled either through a daemon setting
// or otherwise the client-side DOCKER_BUILDKIT environment variable
func BuildKitEnabled(si ServerInfo) (bool, error) {
buildkitEnabled := si.BuildkitVersion == types.BuilderBuildKit
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); buildkitEnv != "" {
var err error
buildkitEnabled, err = strconv.ParseBool(buildkitEnv)
if err != nil {
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
}
}
return buildkitEnabled, nil
}
// ManifestStore returns a store for local manifests
func (cli *DockerCli) ManifestStore() manifeststore.Store {
// TODO: support override default location from config file
@ -205,6 +224,7 @@ func (cli *DockerCli) initializeFromClient() {
cli.serverInfo = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
BuildkitVersion: ping.BuilderVersion,
}
cli.client.NegotiateAPIVersionPing(ping)
}
@ -228,11 +248,17 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
}
// NewContainerizedEngineClient returns a containerized engine client
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error) {
return cli.newContainerizeClient(sockPath)
}
// ServerInfo stores details about the supported features and platform of the
// server
type ServerInfo struct {
HasExperimental bool
OSType string
BuildkitVersion types.BuilderVersion
}
// ClientInfo stores details about the supported features of the client
@ -242,8 +268,8 @@ type ClientInfo struct {
}
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool, containerizedFn func(string) (clitypes.ContainerizedClient, error)) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted, newContainerizeClient: containerizedFn}
}
// NewAPIClientFromFlags creates a new APIClient from command line flags
@ -252,24 +278,43 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
if err != nil {
return &client.Client{}, err
}
var clientOpts []func(*client.Client) error
helper, err := connhelper.GetConnectionHelper(host)
if err != nil {
return &client.Client{}, err
}
if helper == nil {
clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions))
clientOpts = append(clientOpts, client.WithHost(host))
} else {
clientOpts = append(clientOpts, func(c *client.Client) error {
httpClient := &http.Client{
// No tls
// No proxy
Transport: &http.Transport{
DialContext: helper.Dialer,
},
}
return client.WithHTTPClient(httpClient)(c)
})
clientOpts = append(clientOpts, client.WithHost(helper.Host))
clientOpts = append(clientOpts, client.WithDialContext(helper.Dialer))
}
customHeaders := configFile.HTTPHeaders
if customHeaders == nil {
customHeaders = map[string]string{}
}
customHeaders["User-Agent"] = UserAgent()
clientOpts = append(clientOpts, client.WithHTTPHeaders(customHeaders))
verStr := api.DefaultVersion
if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" {
verStr = tmpStr
}
clientOpts = append(clientOpts, client.WithVersion(verStr))
return client.NewClientWithOpts(
withHTTPClient(opts.TLSOptions),
client.WithHTTPHeaders(customHeaders),
client.WithVersion(verStr),
client.WithHost(host),
)
return client.NewClientWithOpts(clientOpts...)
}
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {

View File

@ -43,6 +43,26 @@ func TestNewAPIClientFromFlags(t *testing.T) {
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
}
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
host := ":2375"
opts := &flags.CommonOptions{Hosts: []string{host}}
configFile := &configfile.ConfigFile{
HTTPHeaders: map[string]string{
"My-Header": "Custom-Value",
},
}
apiclient, err := NewAPIClientFromFlags(opts, configFile)
assert.NilError(t, err)
assert.Check(t, is.Equal("tcp://localhost"+host, apiclient.DaemonHost()))
expectedHeaders := map[string]string{
"My-Header": "Custom-Value",
"User-Agent": UserAgent(),
}
assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
}
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
customVersion := "v3.3.3"
defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()

View File

@ -2,11 +2,14 @@ package commands
import (
"os"
"runtime"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/builder"
"github.com/docker/cli/cli/command/checkpoint"
"github.com/docker/cli/cli/command/config"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/engine"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/manifest"
"github.com/docker/cli/cli/command/network"
@ -40,6 +43,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
image.NewImageCommand(dockerCli),
image.NewBuildCommand(dockerCli),
// builder
builder.NewBuilderCommand(dockerCli),
// manifest
manifest.NewManifestCommand(dockerCli),
@ -116,7 +122,10 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
hide(image.NewSaveCommand(dockerCli)),
hide(image.NewTagCommand(dockerCli)),
)
if runtime.GOOS == "linux" {
// engine
cmd.AddCommand(engine.NewEngineCommand(dockerCli))
}
}
func hide(cmd *cobra.Command) *cobra.Command {

View File

@ -9,19 +9,10 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"vbom.ml/util/sortorder"
)
type byConfigName []swarm.Config
func (r byConfigName) Len() int { return len(r) }
func (r byConfigName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byConfigName) Less(i, j int) bool {
return sortorder.NaturalLess(r[i].Spec.Name, r[j].Spec.Name)
}
type listOptions struct {
quiet bool
format string
@ -67,7 +58,9 @@ func runConfigList(dockerCli command.Cli, options listOptions) error {
}
}
sort.Sort(byConfigName(configs))
sort.Slice(configs, func(i, j int) bool {
return sortorder.NaturalLess(configs[i].Spec.Name, configs[j].Spec.Name)
})
configCtx := formatter.Context{
Output: dockerCli.Out(),

View File

@ -370,9 +370,24 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
entrypoint = []string{""}
}
ports, portBindings, err := nat.ParsePortSpecs(copts.publish.GetAll())
publishOpts := copts.publish.GetAll()
var ports map[nat.Port]struct{}
var portBindings map[nat.Port][]nat.PortBinding
ports, portBindings, err = nat.ParsePortSpecs(publishOpts)
// If simple port parsing fails try to parse as long format
if err != nil {
return nil, err
publishOpts, err = parsePortOpts(publishOpts)
if err != nil {
return nil, err
}
ports, portBindings, err = nat.ParsePortSpecs(publishOpts)
if err != nil {
return nil, err
}
}
// Merge in exposed ports to the map of published ports
@ -661,6 +676,23 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
}, nil
}
func parsePortOpts(publishOpts []string) ([]string, error) {
optsList := []string{}
for _, publish := range publishOpts {
params := map[string]string{"protocol": "tcp"}
for _, param := range strings.Split(publish, ",") {
opt := strings.Split(param, "=")
if len(opt) < 2 {
return optsList, errors.Errorf("invalid publish opts format (should be name=value but got '%s')", param)
}
params[opt[0]] = opt[1]
}
optsList = append(optsList, fmt.Sprintf("%s:%s/%s", params["target"], params["published"], params["protocol"]))
}
return optsList, nil
}
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
if loggingDriver == "none" && len(loggingOpts) > 0 {

View File

@ -42,7 +42,6 @@ func TestValidateAttach(t *testing.T) {
}
}
// nolint: unparam
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
flags, copts := setupRunFlags()
if err := flags.Parse(args); err != nil {

View File

@ -73,6 +73,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
// RunPrune calls the Container Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
}

View File

@ -0,0 +1,209 @@
package engine
import (
"context"
"fmt"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/licenseutils"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
"github.com/docker/licensing/model"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type activateOptions struct {
licenseFile string
version string
registryPrefix string
format string
image string
quiet bool
displayOnly bool
sockPath string
licenseLoginFunc func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error)
}
// newActivateCommand creates a new `docker engine activate` command
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
var options activateOptions
options.licenseLoginFunc = licenseutils.Login
cmd := &cobra.Command{
Use: "activate [OPTIONS]",
Short: "Activate Enterprise Edition",
Long: `Activate Enterprise Edition.
With this command you may apply an existing Docker enterprise license, or
interactively download one from Docker. In the interactive exchange, you can
sign up for a new trial, or download an existing license. If you are
currently running a Community Edition engine, the daemon will be updated to
the Enterprise Edition Docker engine with additional capabilities and long
term support.
For more information about different Docker Enterprise license types visit
https://www.docker.com/licenses
For non-interactive scriptable deployments, download your license from
https://hub.docker.com/ then specify the file with the '--license' flag.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return runActivate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVar(&options.licenseFile, "license", "", "License File")
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled")
flags.StringVar(&options.image, "engine-image", "", "Specify engine image")
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
flags.BoolVar(&options.displayOnly, "display-only", false, "only display license information and exit")
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runActivate(cli command.Cli, options activateOptions) error {
if !isRoot() {
return errors.New("this command must be run as a privileged user")
}
ctx := context.Background()
client, err := cli.NewContainerizedEngineClient(options.sockPath)
if err != nil {
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
authConfig, err := getRegistryAuth(cli, options.registryPrefix)
if err != nil {
return err
}
var license *model.IssuedLicense
// Lookup on hub if no license provided via params
if options.licenseFile == "" {
if license, err = getLicenses(ctx, authConfig, cli, options); err != nil {
return err
}
if options.displayOnly {
return nil
}
} else {
if license, err = licenseutils.LoadLocalIssuedLicense(ctx, options.licenseFile); err != nil {
return err
}
}
summary, err := licenseutils.GetLicenseSummary(ctx, *license)
if err != nil {
return err
}
fmt.Fprintf(cli.Out(), "License: %s\n", summary)
if options.displayOnly {
return nil
}
dclient := cli.Client()
if err = licenseutils.ApplyLicense(ctx, dclient, license); err != nil {
return err
}
// Short circuit if the user didn't specify a version and we're already running enterprise
if options.version == "" {
serverVersion, err := dclient.ServerVersion(ctx)
if err != nil {
return err
}
if strings.Contains(strings.ToLower(serverVersion.Platform.Name), "enterprise") {
fmt.Fprintln(cli.Out(), "Successfully activated engine license on existing enterprise engine.")
return nil
}
options.version = serverVersion.Version
}
opts := clitypes.EngineInitOptions{
RegistryPrefix: options.registryPrefix,
EngineImage: options.image,
EngineVersion: options.version,
}
if err := client.ActivateEngine(ctx, opts, cli.Out(), authConfig); err != nil {
return err
}
fmt.Fprintln(cli.Out(), `Successfully activated engine.
Restart docker with 'systemctl restart docker' to complete the activation.`)
return nil
}
func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
user, err := options.licenseLoginFunc(ctx, authConfig)
if err != nil {
return nil, err
}
fmt.Fprintf(cli.Out(), "Looking for existing licenses for %s...\n", user.User.Username)
subs, err := user.GetAvailableLicenses(ctx)
if err != nil {
return nil, err
}
if len(subs) == 0 {
return doTrialFlow(ctx, cli, user)
}
format := options.format
if len(format) == 0 {
format = formatter.TableFormatKey
}
updatesCtx := formatter.Context{
Output: cli.Out(),
Format: formatter.NewSubscriptionsFormat(format, options.quiet),
Trunc: false,
}
if err := formatter.SubscriptionsWrite(updatesCtx, subs); err != nil {
return nil, err
}
if options.displayOnly {
return nil, nil
}
fmt.Fprintf(cli.Out(), "Please pick a license by number: ")
var num int
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
return nil, errors.Wrap(err, "failed to read user input")
}
if num < 0 || num >= len(subs) {
return nil, fmt.Errorf("invalid choice")
}
return user.GetIssuedLicense(ctx, subs[num].ID)
}
func doTrialFlow(ctx context.Context, cli command.Cli, user licenseutils.HubUser) (*model.IssuedLicense, error) {
if !command.PromptForConfirmation(cli.In(), cli.Out(),
"No existing licenses found, would you like to set up a new Enterprise Basic Trial license?") {
return nil, fmt.Errorf("you must have an existing enterprise license or generate a new trial to use the Enterprise Docker Engine")
}
targetID := user.User.ID
// If the user is a member of any organizations, allow trials generated against them
if len(user.Orgs) > 0 {
fmt.Fprintf(cli.Out(), "%d\t%s\n", 0, user.User.Username)
for i, org := range user.Orgs {
fmt.Fprintf(cli.Out(), "%d\t%s\n", i+1, org.Orgname)
}
fmt.Fprintf(cli.Out(), "Please choose an account to generate the trial in:")
var num int
if _, err := fmt.Fscan(cli.In(), &num); err != nil {
return nil, errors.Wrap(err, "failed to read user input")
}
if num < 0 || num > len(user.Orgs) {
return nil, fmt.Errorf("invalid choice")
}
if num > 0 {
targetID = user.Orgs[num-1].ID
}
}
return user.GenerateTrialLicense(ctx, targetID)
}

View File

@ -0,0 +1,146 @@
package engine
import (
"context"
"fmt"
"testing"
"time"
"github.com/docker/cli/internal/licenseutils"
"github.com/docker/cli/internal/test"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/licensing"
"github.com/docker/licensing/model"
"gotest.tools/assert"
"gotest.tools/fs"
"gotest.tools/golden"
)
const (
// nolint: lll
expiredLicense = `{"key_id":"irlYm3b9fdD8hMUXjazF39im7VQSSbAm9tfHK8cKUxJt","private_key":"aH5tTRDAVJpCRS2CRetTQVXIKgWUPfoCHODhDvNPvAbz","authorization":"ewogICAicGF5bG9hZCI6ICJleUpsZUhCcGNtRjBhVzl1SWpvaU1qQXhPQzB3TXkweE9GUXdOem93TURvd01Gb2lMQ0owYjJ0bGJpSTZJbkZtTVMxMlVtRmtialp5YjFaMldXdHJlVXN4VFdKMGNGUmpXR1ozVjA4MVRWZFFTM2cwUnpJd2NIYzlJaXdpYldGNFJXNW5hVzVsY3lJNk1Td2ljMk5oYm01cGJtZEZibUZpYkdWa0lqcDBjblZsTENKc2FXTmxibk5sVkhsd1pTSTZJazltWm14cGJtVWlMQ0owYVdWeUlqb2lVSEp2WkhWamRHbHZiaUo5IiwKICAgInNpZ25hdHVyZXMiOiBbCiAgICAgIHsKICAgICAgICAgImhlYWRlciI6IHsKICAgICAgICAgICAgImp3ayI6IHsKICAgICAgICAgICAgICAgImUiOiAiQVFBQiIsCiAgICAgICAgICAgICAgICJrZXlJRCI6ICJKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVCIsCiAgICAgICAgICAgICAgICJraWQiOiAiSjdMRDo2N1ZSOkw1SFo6VTdCQToyTzRHOjRBTDM6T0YyTjpKSEdCOkVGVEg6NUNWUTpNRkVPOkFFSVQiLAogICAgICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICAgICJuIjogInlkSXktbFU3bzdQY2VZLTQtcy1DUTVPRWdDeUY4Q3hJY1FJV3VLODRwSWlaY2lZNjczMHlDWW53TFNLVGx3LVU2VUNfUVJlV1Jpb01OTkU1RHM1VFlFWGJHRzZvbG0ycWRXYkJ3Y0NnLTJVVUhfT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTFJdi1tcTdscDVuUV9SeHJUMEtaUkFRVFlMRU1FZkd3bTNoTU9fZ2VMUFMtaGdLUHRJSGxrZzZfV2NveFRHb0tQNzlkX3dhSFl4R05sN1doU25laUJTeGJwYlFBS2syMWxnNzk4WGI3dlp5RUFURE1yUlI5TWVFNkFkajVISnBZM0NveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTy13YTJLTjhVd2dJSW0waTVJMXVXOUdrcTR6akJ5NXpoZ3F1VVhiRzliV1BBT1lycTVRYTgxRHhHY0JsSnlIWUFwLUREUEU5VEdnNHpZbVhqSm54WnFIRWR1R3FkZXZaOFhNSTB1a2ZrR0lJMTR3VU9pTUlJSXJYbEVjQmZfNDZJOGdRV0R6eHljWmVfSkdYLUxBdWF5WHJ5clVGZWhWTlVkWlVsOXdYTmFKQi1rYUNxejVRd2FSOTNzR3ctUVNmdEQwTnZMZTdDeU9ILUU2dmc2U3RfTmVUdmd2OFluaENpWElsWjhIT2ZJd05lN3RFRl9VY3o1T2JQeWttM3R5bHJOVWp0MFZ5QW10dGFjVkkyaUdpaGNVUHJtazRsVklaN1ZEX0xTVy1pN3lvU3VydHBzUFhjZTJwS0RJbzMwbEpHaE9fM0tVbWwyU1VaQ3F6SjF5RW1LcHlzSDVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiYWxnIjogIlJTMjU2IgogICAgICAgICB9LAogICAgICAgICAic2lnbmF0dXJlIjogIm5saTZIdzRrbW5KcTBSUmRXaGVfbkhZS2VJLVpKenM1U0d5SUpDakh1dWtnVzhBYklpVzFZYWJJR2NqWUt0QTY4dWN6T1hyUXZreGxWQXJLSlgzMDJzN0RpbzcxTlNPRzJVcnhsSjlibDFpd0F3a3ZyTEQ2T0p5MGxGLVg4WnRabXhPVmNQZmwzcmJwZFQ0dnlnWTdNcU1QRXdmb0IxTmlWZDYyZ1cxU2NSREZZcWw3R0FVaFVKNkp4QU15VzVaOXl5YVE0NV8wd0RMUk5mRjA5YWNXeVowTjRxVS1hZjhrUTZUUWZUX05ERzNCR3pRb2V3cHlEajRiMFBHb0diOFhLdDlwekpFdEdxM3lQM25VMFFBbk90a2gwTnZac1l1UFcyUnhDT3lRNEYzVlR3UkF2eF9HSTZrMVRpYmlKNnByUWluUy16Sjh6RE8zUjBuakE3OFBwNXcxcVpaUE9BdmtzZFNSYzJDcVMtcWhpTmF5YUhOVHpVNnpyOXlOZHR2S0o1QjNST0FmNUtjYXNiWURjTnVpeXBUNk90LUtqQ2I1dmYtWVpnc2FRNzJBdFBhSU4yeUpNREZHbmEwM0hpSjMxcTJRUlp5eTZrd3RYaGtwcDhTdEdIcHYxSWRaV09SVWttb0g5SFBzSGk4SExRLTZlM0tEY2x1RUQyMTNpZnljaVhtN0YzdHdaTTNHeDd1UXR1SldHaUlTZ2Z0QW9lVjZfUmI2VThkMmZxNzZuWHYxak5nckRRcE5waEZFd2tCdGRtZHZ2THByZVVYX3BWangza1AxN3pWbXFKNmNOOWkwWUc4WHg2VmRzcUxsRXUxQ2Rhd3Q0eko1M3VHMFlKTjRnUDZwc25yUS1uM0U1aFdlMDJ3d3dBZ3F3bGlPdmd4V1RTeXJyLXY2eDI0IiwKICAgICAgICAgInByb3RlY3RlZCI6ICJleUptYjNKdFlYUk1aVzVuZEdnaU9qRTNNeXdpWm05eWJXRjBWR0ZwYkNJNkltWlJJaXdpZEdsdFpTSTZJakl3TVRjdE1EVXRNRFZVTWpFNk5UYzZNek5hSW4wIgogICAgICB9CiAgIF0KfQ=="}`
)
func TestActivateNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return nil, fmt.Errorf("some error")
},
)
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli)
cmd.Flags().Set("license", "invalidpath")
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}
func TestActivateBadLicense(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
cmd.Flags().Set("license", "invalidpath")
err := cmd.Execute()
assert.Error(t, err, "open invalidpath: no such file or directory")
}
func TestActivateExpiredLicenseDryRun(t *testing.T) {
dir := fs.NewDir(t, "license", fs.WithFile("docker.lic", expiredLicense, fs.WithMode(0644)))
defer dir.Remove()
filename := dir.Join("docker.lic")
isRoot = func() bool { return true }
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newActivateCommand(c)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
cmd.Flags().Set("license", filename)
cmd.Flags().Set("display-only", "true")
c.OutBuffer().Reset()
err := cmd.Execute()
assert.NilError(t, err)
golden.Assert(t, c.OutBuffer().String(), "expired-license-display-only.golden")
}
type mockLicenseClient struct{}
func (c mockLicenseClient) LoginViaAuth(ctx context.Context, username, password string) (authToken string, err error) {
return "", fmt.Errorf("not implemented")
}
func (c mockLicenseClient) GetHubUserOrgs(ctx context.Context, authToken string) (orgs []model.Org, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) GetHubUserByName(ctx context.Context, username string) (user *model.User, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) VerifyLicense(ctx context.Context, license model.IssuedLicense) (res *model.CheckResponse, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID string) (subscriptionID string, err error) {
return "", fmt.Errorf("not implemented")
}
func (c mockLicenseClient) ListSubscriptions(ctx context.Context, authToken, dockerID string) (response []*model.Subscription, err error) {
expires := time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC)
return []*model.Subscription{
{
State: "active",
Expires: &expires,
},
}, nil
}
func (c mockLicenseClient) ListSubscriptionsDetails(ctx context.Context, authToken, dockerID string) (response []*model.SubscriptionDetail, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) DownloadLicenseFromHub(ctx context.Context, authToken, subscriptionID string) (license *model.IssuedLicense, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) ParseLicense(license []byte) (parsedLicense *model.IssuedLicense, err error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) StoreLicense(ctx context.Context, dclnt licensing.WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error {
return fmt.Errorf("not implemented")
}
func (c mockLicenseClient) LoadLocalLicense(ctx context.Context, dclnt licensing.WrappedDockerClient) (*model.Subscription, error) {
return nil, fmt.Errorf("not implemented")
}
func (c mockLicenseClient) SummarizeLicense(res *model.CheckResponse, keyID string) *model.Subscription {
return nil
}
func TestActivateDisplayOnlyHub(t *testing.T) {
isRoot = func() bool { return true }
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
hubUser := licenseutils.HubUser{
Client: mockLicenseClient{},
}
options := activateOptions{
licenseLoginFunc: func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error) {
return hubUser, nil
},
displayOnly: true,
}
c.OutBuffer().Reset()
err := runActivate(c, options)
assert.NilError(t, err)
golden.Assert(t, c.OutBuffer().String(), "expired-hub-license-display-only.golden")
}

View File

@ -0,0 +1,13 @@
// +build !windows
package engine
import (
"golang.org/x/sys/unix"
)
var (
isRoot = func() bool {
return unix.Geteuid() == 0
}
)

View File

@ -0,0 +1,9 @@
// +build windows
package engine
var (
isRoot = func() bool {
return true
}
)

View File

@ -0,0 +1,34 @@
package engine
import (
"context"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/pkg/errors"
)
func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
if registryPrefix == "" {
registryPrefix = clitypes.RegistryPrefix
}
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse image name: %s", registryPrefix)
}
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(context.Background(), nil, authResolver(cli), distributionRef.String())
if err != nil {
return nil, errors.Wrap(err, "failed to get imgRefAndAuth")
}
return imgRefAndAuth.AuthConfig(), nil
}
func authResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
}

125
cli/command/engine/check.go Normal file
View File

@ -0,0 +1,125 @@
package engine
import (
"context"
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type checkOptions struct {
registryPrefix string
preReleases bool
engineImage string
downgrades bool
upgrades bool
format string
quiet bool
sockPath string
}
func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
var options checkOptions
cmd := &cobra.Command{
Use: "check [OPTIONS]",
Short: "Check for available engine updates",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runCheck(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the existing location where engine images are pulled")
flags.BoolVar(&options.downgrades, "downgrades", false, "Report downgrades (default omits older versions)")
flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release versions")
flags.StringVar(&options.engineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runCheck(dockerCli command.Cli, options checkOptions) error {
if !isRoot() {
return errors.New("this command must be run as a privileged user")
}
ctx := context.Background()
client := dockerCli.Client()
serverVersion, err := client.ServerVersion(ctx)
if err != nil {
return err
}
availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, options.engineImage, serverVersion.Version)
if err != nil {
return err
}
availUpdates := []clitypes.Update{
{Type: "current", Version: serverVersion.Version},
}
if len(availVersions.Patches) > 0 {
availUpdates = append(availUpdates,
processVersions(
serverVersion.Version,
"patch",
options.preReleases,
availVersions.Patches)...)
}
if options.upgrades {
availUpdates = append(availUpdates,
processVersions(
serverVersion.Version,
"upgrade",
options.preReleases,
availVersions.Upgrades)...)
}
if options.downgrades {
availUpdates = append(availUpdates,
processVersions(
serverVersion.Version,
"downgrade",
options.preReleases,
availVersions.Downgrades)...)
}
format := options.format
if len(format) == 0 {
format = formatter.TableFormatKey
}
updatesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewUpdatesFormat(format, options.quiet),
Trunc: false,
}
return formatter.UpdatesWrite(updatesCtx, availUpdates)
}
func processVersions(currentVersion, verType string,
includePrerelease bool,
availVersions []clitypes.DockerVersion) []clitypes.Update {
availUpdates := []clitypes.Update{}
for _, ver := range availVersions {
if !includePrerelease && ver.Prerelease() != "" {
continue
}
if ver.Tag != currentVersion {
availUpdates = append(availUpdates, clitypes.Update{
Type: verType,
Version: ver.Tag,
Notes: fmt.Sprintf("%s?%s", clitypes.ReleaseNotePrefix, ver.Tag),
})
}
}
return availUpdates
}

View File

@ -0,0 +1,114 @@
package engine
import (
"context"
"fmt"
"testing"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/distribution"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/opencontainers/go-digest"
"gotest.tools/assert"
"gotest.tools/golden"
)
var (
testCli = test.NewFakeCli(&client.Client{})
)
type verClient struct {
client.Client
ver types.Version
verErr error
info types.Info
infoErr error
}
func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) {
return c.ver, c.verErr
}
func (c *verClient) Info(ctx context.Context) (types.Info, error) {
return c.info, c.infoErr
}
type testRegistryClient struct {
tags []string
}
func (c testRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, nil
}
func (c testRegistryClient) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, nil
}
func (c testRegistryClient) MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error {
return nil
}
func (c testRegistryClient) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
return "", nil
}
func (c testRegistryClient) GetTags(ctx context.Context, ref reference.Named) ([]string, error) {
return c.tags, nil
}
func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
isRoot = func() bool { return true }
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
c.SetRegistryClient(testRegistryClient{})
cmd := newCheckForUpdatesCommand(c)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "no such file or directory")
}
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
c.SetRegistryClient(testRegistryClient{[]string{
"1.0.1", "1.0.2", "1.0.3-beta1",
"1.1.1", "1.1.2", "1.1.3-beta1",
"1.2.0", "2.0.0", "2.1.0-beta1",
}})
isRoot = func() bool { return true }
cmd := newCheckForUpdatesCommand(c)
cmd.Flags().Set("pre-releases", "true")
cmd.Flags().Set("downgrades", "true")
cmd.Flags().Set("engine-image", "engine-community")
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.NilError(t, err)
golden.Assert(t, c.OutBuffer().String(), "check-all.golden")
c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "true")
err = cmd.Execute()
assert.NilError(t, err)
fmt.Println(c.OutBuffer().String())
golden.Assert(t, c.OutBuffer().String(), "check-no-prerelease.golden")
c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false")
err = cmd.Execute()
assert.NilError(t, err)
fmt.Println(c.OutBuffer().String())
golden.Assert(t, c.OutBuffer().String(), "check-no-downgrades.golden")
c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false")
cmd.Flags().Set("upgrades", "false")
err = cmd.Execute()
assert.NilError(t, err)
fmt.Println(c.OutBuffer().String())
golden.Assert(t, c.OutBuffer().String(), "check-patches-only.golden")
}

View File

@ -0,0 +1,101 @@
package engine
import (
"context"
"github.com/containerd/containerd"
registryclient "github.com/docker/cli/cli/registry/client"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
)
type (
fakeContainerizedEngineClient struct {
closeFunc func() error
activateEngineFunc func(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig) error
initEngineFunc func(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig,
healthfn func(context.Context) error) error
doUpdateFunc func(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig) error
getEngineVersionsFunc func(ctx context.Context,
registryClient registryclient.RegistryClient,
currentVersion,
imageName string) (clitypes.AvailableVersions, error)
getEngineFunc func(ctx context.Context) (containerd.Container, error)
removeEngineFunc func(ctx context.Context) error
getCurrentEngineVersionFunc func(ctx context.Context) (clitypes.EngineInitOptions, error)
}
)
func (w *fakeContainerizedEngineClient) Close() error {
if w.closeFunc != nil {
return w.closeFunc()
}
return nil
}
func (w *fakeContainerizedEngineClient) ActivateEngine(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig) error {
if w.activateEngineFunc != nil {
return w.activateEngineFunc(ctx, opts, out, authConfig)
}
return nil
}
func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig,
healthfn func(context.Context) error) error {
if w.initEngineFunc != nil {
return w.initEngineFunc(ctx, opts, out, authConfig, healthfn)
}
return nil
}
func (w *fakeContainerizedEngineClient) DoUpdate(ctx context.Context,
opts clitypes.EngineInitOptions,
out clitypes.OutStream,
authConfig *types.AuthConfig) error {
if w.doUpdateFunc != nil {
return w.doUpdateFunc(ctx, opts, out, authConfig)
}
return nil
}
func (w *fakeContainerizedEngineClient) GetEngineVersions(ctx context.Context,
registryClient registryclient.RegistryClient,
currentVersion, imageName string) (clitypes.AvailableVersions, error) {
if w.getEngineVersionsFunc != nil {
return w.getEngineVersionsFunc(ctx, registryClient, currentVersion, imageName)
}
return clitypes.AvailableVersions{}, nil
}
func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containerd.Container, error) {
if w.getEngineFunc != nil {
return w.getEngineFunc(ctx)
}
return nil, nil
}
func (w *fakeContainerizedEngineClient) RemoveEngine(ctx context.Context) error {
if w.removeEngineFunc != nil {
return w.removeEngineFunc(ctx)
}
return nil
}
func (w *fakeContainerizedEngineClient) GetCurrentEngineVersion(ctx context.Context) (clitypes.EngineInitOptions, error) {
if w.getCurrentEngineVersionFunc != nil {
return w.getCurrentEngineVersionFunc(ctx)
}
return clitypes.EngineInitOptions{}, nil
}

23
cli/command/engine/cmd.go Normal file
View File

@ -0,0 +1,23 @@
package engine
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
// NewEngineCommand returns a cobra command for `engine` subcommands
func NewEngineCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "engine COMMAND",
Short: "Manage the docker engine",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newActivateCommand(dockerCli),
newCheckForUpdatesCommand(dockerCli),
newUpdateCommand(dockerCli),
)
return cmd
}

View File

@ -0,0 +1,14 @@
package engine
import (
"testing"
"gotest.tools/assert"
)
func TestNewEngineCommand(t *testing.T) {
cmd := NewEngineCommand(testCli)
subcommands := cmd.Commands()
assert.Assert(t, len(subcommands) == 3)
}

View File

@ -0,0 +1,10 @@
package engine
import (
clitypes "github.com/docker/cli/types"
)
type extendedEngineInitOptions struct {
clitypes.EngineInitOptions
sockPath string
}

View File

@ -0,0 +1,11 @@
TYPE VERSION NOTES
current 1.1.0
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
patch 1.1.3-beta1 https://docker.com/engine/releasenotes?1.1.3-beta1
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
upgrade 2.1.0-beta1 https://docker.com/engine/releasenotes?2.1.0-beta1
downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2
downgrade 1.0.3-beta1 https://docker.com/engine/releasenotes?1.0.3-beta1

View File

@ -0,0 +1,6 @@
TYPE VERSION NOTES
current 1.1.0
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0

View File

@ -0,0 +1,8 @@
TYPE VERSION NOTES
current 1.1.0
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2

View File

@ -0,0 +1,4 @@
TYPE VERSION NOTES
current 1.1.0
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2

View File

@ -0,0 +1,3 @@
Looking for existing licenses for ...
NUM OWNER PRODUCT ID EXPIRES PRICING COMPONENTS
0 2010-01-01 00:00:00 +0000 UTC

View File

@ -0,0 +1 @@
License: Quantity: 1 Nodes Expiration date: 2018-03-18 Expired! You will no longer receive updates. Please renew at https://docker.com/licensing

View File

@ -0,0 +1,55 @@
package engine
import (
"context"
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
clitypes "github.com/docker/cli/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
var options extendedEngineInitOptions
cmd := &cobra.Command{
Use: "update [OPTIONS]",
Short: "Update a local engine",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runUpdate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version")
flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
flags.StringVar(&options.RegistryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the current location where engine images are pulled")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
if !isRoot() {
return errors.New("this command must be run as a privileged user")
}
ctx := context.Background()
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
if err != nil {
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
if err != nil {
return err
}
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig); err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), `Successfully updated engine.
Restart docker with 'systemctl restart docker' to complete the update.`)
return nil
}

View File

@ -0,0 +1,40 @@
package engine
import (
"fmt"
"testing"
"github.com/docker/cli/internal/test"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"gotest.tools/assert"
)
func TestUpdateNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return nil, fmt.Errorf("some error")
},
)
cmd := newUpdateCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}
func TestUpdateHappy(t *testing.T) {
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newUpdateCommand(c)
cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix)
cmd.Flags().Set("version", "someversion")
cmd.Flags().Set("engine-image", "someimage")
err := cmd.Execute()
assert.NilError(t, err)
}

View File

@ -0,0 +1,179 @@
package formatter
import (
"fmt"
"sort"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/go-units"
)
const (
defaultBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}\t{{.Description}}"
cacheIDHeader = "CACHE ID"
cacheTypeHeader = "CACHE TYPE"
parentHeader = "PARENT"
lastUsedSinceHeader = "LAST USED"
usageCountHeader = "USAGE"
inUseHeader = "IN USE"
sharedHeader = "SHARED"
)
// NewBuildCacheFormat returns a Format for rendering using a Context
func NewBuildCacheFormat(source string, quiet bool) Format {
switch source {
case TableFormatKey:
if quiet {
return defaultQuietFormat
}
return Format(defaultBuildCacheTableFormat)
case RawFormatKey:
if quiet {
return `build_cache_id: {{.ID}}`
}
format := `build_cache_id: {{.ID}}
parent_id: {{.Parent}}
build_cache_type: {{.CacheType}}
description: {{.Description}}
created_at: {{.CreatedAt}}
created_since: {{.CreatedSince}}
last_used_at: {{.LastUsedAt}}
last_used_since: {{.LastUsedSince}}
usage_count: {{.UsageCount}}
in_use: {{.InUse}}
shared: {{.Shared}}
`
return Format(format)
}
return Format(source)
}
func buildCacheSort(buildCache []*types.BuildCache) {
sort.Slice(buildCache, func(i, j int) bool {
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
switch {
case lui == nil && luj == nil:
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
case lui == nil:
return true
case luj == nil:
return false
case lui.Equal(*luj):
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
default:
return lui.Before(*luj)
}
})
}
// BuildCacheWrite renders the context for a list of containers
func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error {
render := func(format func(subContext subContext) error) error {
buildCacheSort(buildCaches)
for _, bc := range buildCaches {
err := format(&buildCacheContext{trunc: ctx.Trunc, v: bc})
if err != nil {
return err
}
}
return nil
}
return ctx.Write(newBuildCacheContext(), render)
}
type buildCacheHeaderContext map[string]string
type buildCacheContext struct {
HeaderContext
trunc bool
v *types.BuildCache
}
func newBuildCacheContext() *buildCacheContext {
buildCacheCtx := buildCacheContext{}
buildCacheCtx.header = buildCacheHeaderContext{
"ID": cacheIDHeader,
"Parent": parentHeader,
"CacheType": cacheTypeHeader,
"Size": sizeHeader,
"CreatedSince": createdSinceHeader,
"LastUsedSince": lastUsedSinceHeader,
"UsageCount": usageCountHeader,
"InUse": inUseHeader,
"Shared": sharedHeader,
"Description": descriptionHeader,
}
return &buildCacheCtx
}
func (c *buildCacheContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *buildCacheContext) ID() string {
id := c.v.ID
if c.trunc {
id = stringid.TruncateID(c.v.ID)
}
if c.v.InUse {
return id + "*"
}
return id
}
func (c *buildCacheContext) Parent() string {
if c.trunc {
return stringid.TruncateID(c.v.Parent)
}
return c.v.Parent
}
func (c *buildCacheContext) CacheType() string {
return c.v.Type
}
func (c *buildCacheContext) Description() string {
return c.v.Description
}
func (c *buildCacheContext) Size() string {
return units.HumanSizeWithPrecision(float64(c.v.Size), 3)
}
func (c *buildCacheContext) CreatedAt() string {
return c.v.CreatedAt.String()
}
func (c *buildCacheContext) CreatedSince() string {
return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago"
}
func (c *buildCacheContext) LastUsedAt() string {
if c.v.LastUsedAt == nil {
return ""
}
return c.v.LastUsedAt.String()
}
func (c *buildCacheContext) LastUsedSince() string {
if c.v.LastUsedAt == nil {
return ""
}
return units.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago"
}
func (c *buildCacheContext) UsageCount() string {
return fmt.Sprintf("%d", c.v.UsageCount)
}
func (c *buildCacheContext) InUse() string {
return fmt.Sprintf("%t", c.v.InUse)
}
func (c *buildCacheContext) Shared() string {
return fmt.Sprintf("%t", c.v.Shared)
}

View File

@ -269,7 +269,10 @@ func DisplayablePorts(ports []types.Port) string {
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Sort(byPortInfo(ports))
sort.Slice(ports, func(i, j int) bool {
return comparePorts(ports[i], ports[j])
})
for _, port := range ports {
current := port.PrivatePort
portKey := port.Type
@ -322,23 +325,18 @@ func formGroup(key string, start, last uint16) string {
return fmt.Sprintf("%s/%s", group, groupType)
}
// byPortInfo is a temporary type used to sort types.Port by its fields
type byPortInfo []types.Port
func (r byPortInfo) Len() int { return len(r) }
func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byPortInfo) Less(i, j int) bool {
if r[i].PrivatePort != r[j].PrivatePort {
return r[i].PrivatePort < r[j].PrivatePort
func comparePorts(i, j types.Port) bool {
if i.PrivatePort != j.PrivatePort {
return i.PrivatePort < j.PrivatePort
}
if r[i].IP != r[j].IP {
return r[i].IP < r[j].IP
if i.IP != j.IP {
return i.IP < j.IP
}
if r[i].PublicPort != r[j].PublicPort {
return r[i].PublicPort < r[j].PublicPort
if i.PublicPort != j.PublicPort {
return i.PublicPort < j.PublicPort
}
return r[i].Type < r[j].Type
return i.Type < j.Type
}

View File

@ -12,19 +12,11 @@ import (
)
const (
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
defaultBuildCacheVerboseFormat = `
ID: {{.ID}}
Description: {{.Description}}
Mutable: {{.Mutable}}
Size: {{.Size}}
CreatedAt: {{.CreatedAt}}
LastUsedAt: {{.LastUsedAt}}
UsageCount: {{.UsageCount}}
`
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
typeHeader = "TYPE"
totalHeader = "TOTAL"
@ -32,7 +24,7 @@ UsageCount: {{.UsageCount}}
reclaimableHeader = "RECLAIMABLE"
containersHeader = "CONTAINERS"
sharedSizeHeader = "SHARED SIZE"
uniqueSizeHeader = "UNIQUE SiZE"
uniqueSizeHeader = "UNIQUE SIZE"
)
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
@ -56,14 +48,26 @@ func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template,
return ctx.parseFormat()
}
//
// NewDiskUsageFormat returns a format for rendering an DiskUsageContext
func NewDiskUsageFormat(source string) Format {
switch source {
case TableFormatKey:
format := defaultDiskUsageTableFormat
return Format(format)
case RawFormatKey:
func NewDiskUsageFormat(source string, verbose bool) Format {
switch {
case verbose && source == RawFormatKey:
format := `{{range .Images}}type: Image
` + NewImageFormat(source, false, true) + `
{{end -}}
{{range .Containers}}type: Container
` + NewContainerFormat(source, false, true) + `
{{end -}}
{{range .Volumes}}type: Volume
` + NewVolumeFormat(source, false) + `
{{end -}}
{{range .BuildCache}}type: Build Cache
` + NewBuildCacheFormat(source, false) + `
{{end -}}`
return format
case !verbose && source == TableFormatKey:
return Format(defaultDiskUsageTableFormat)
case !verbose && source == RawFormatKey:
format := `type: {{.Type}}
total: {{.TotalCount}}
active: {{.Active}}
@ -71,8 +75,9 @@ size: {{.Size}}
reclaimable: {{.Reclaimable}}
`
return Format(format)
default:
return Format(source)
}
return Format(source)
}
func (ctx *DiskUsageContext) Write() (err error) {
@ -129,14 +134,23 @@ func (ctx *DiskUsageContext) Write() (err error) {
return err
}
func (ctx *DiskUsageContext) verboseWrite() error {
// First images
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
if err != nil {
return err
}
type diskUsageContext struct {
Images []*imageContext
Containers []*containerContext
Volumes []*volumeContext
BuildCache []*buildCacheContext
}
ctx.Output.Write([]byte("Images space usage:\n\n"))
func (ctx *DiskUsageContext) verboseWrite() error {
duc := &diskUsageContext{
Images: make([]*imageContext, 0, len(ctx.Images)),
Containers: make([]*containerContext, 0, len(ctx.Containers)),
Volumes: make([]*volumeContext, 0, len(ctx.Volumes)),
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
}
trunc := ctx.Format.IsTable()
// First images
for _, i := range ctx.Images {
repo := "<none>"
tag := "<none>"
@ -152,55 +166,92 @@ func (ctx *DiskUsageContext) verboseWrite() error {
}
}
err := ctx.contextFormat(tmpl, &imageContext{
duc.Images = append(duc.Images, &imageContext{
repo: repo,
tag: tag,
trunc: true,
trunc: trunc,
i: *i,
})
if err != nil {
}
// Now containers
for _, c := range ctx.Containers {
// Don't display the virtual size
c.SizeRootFs = 0
duc.Containers = append(duc.Containers, &containerContext{trunc: trunc, c: *c})
}
// And volumes
for _, v := range ctx.Volumes {
duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
}
// And build cache
buildCacheSort(ctx.BuildCache)
for _, v := range ctx.BuildCache {
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
}
if ctx.Format == TableFormatKey {
return ctx.verboseWriteTable(duc)
}
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
return err
}
return tmpl.Execute(ctx.Output, duc)
}
func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
if err != nil {
return err
}
ctx.Output.Write([]byte("Images space usage:\n\n"))
for _, img := range duc.Images {
if err := ctx.contextFormat(tmpl, img); err != nil {
return err
}
}
ctx.postFormat(tmpl, newImageContext())
// Now containers
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
if err != nil {
return err
}
for _, c := range ctx.Containers {
// Don't display the virtual size
c.SizeRootFs = 0
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
if err != nil {
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
for _, c := range duc.Containers {
if err := ctx.contextFormat(tmpl, c); err != nil {
return err
}
}
ctx.postFormat(tmpl, newContainerContext())
// And volumes
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
if err != nil {
return err
}
for _, v := range ctx.Volumes {
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil {
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
for _, v := range duc.Volumes {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
}
}
ctx.postFormat(tmpl, newVolumeContext())
// And build cache
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
t := template.Must(template.New("buildcache").Parse(defaultBuildCacheVerboseFormat))
for _, v := range ctx.BuildCache {
t.Execute(ctx.Output, *v)
tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
if err != nil {
return err
}
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
for _, v := range duc.BuildCache {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err
}
}
ctx.postFormat(tmpl, newBuildCacheContext())
return nil
}
@ -416,7 +467,7 @@ func (c *diskUsageBuilderContext) Size() string {
func (c *diskUsageBuilderContext) Reclaimable() string {
var inUseBytes int64
for _, bc := range c.buildCache {
if bc.InUse {
if bc.InUse && !bc.Shared {
inUseBytes += bc.Size
}
}

View File

@ -18,7 +18,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
{
DiskUsageContext{
Context: Context{
Format: NewDiskUsageFormat("table"),
Format: NewDiskUsageFormat("table", false),
},
Verbose: false},
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
@ -29,14 +29,14 @@ Build Cache 0 0 0B
`,
},
{
DiskUsageContext{Verbose: true},
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("table", true)}},
`Images space usage:
REPOSITORY TAG IMAGE ID CREATED ago SIZE SHARED SIZE UNIQUE SiZE CONTAINERS
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
Containers space usage:
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED ago STATUS NAMES
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES
Local Volumes space usage:
@ -44,8 +44,17 @@ VOLUME NAME LINKS SIZE
Build cache usage: 0B
CACHE ID CACHE TYPE SIZE CREATED LAST USED USAGE SHARED
`,
},
{
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("raw", true)}},
``,
},
{
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("{{json .}}", true)}},
`{"Images":[],"Containers":[],"Volumes":[],"BuildCache":[]}`,
},
// Errors
{
DiskUsageContext{
@ -69,7 +78,7 @@ Build cache usage: 0B
{
DiskUsageContext{
Context: Context{
Format: NewDiskUsageFormat("table"),
Format: NewDiskUsageFormat("table", false),
},
},
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
@ -82,7 +91,7 @@ Build Cache 0 0 0B
{
DiskUsageContext{
Context: Context{
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"),
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}", false),
},
},
string(golden.Get(t, "disk-usage-context-write-custom.golden")),
@ -91,7 +100,7 @@ Build Cache 0 0 0B
{
DiskUsageContext{
Context: Context{
Format: NewDiskUsageFormat("raw"),
Format: NewDiskUsageFormat("raw", false),
},
},
string(golden.Get(t, "disk-usage-raw-format.golden")),

View File

@ -0,0 +1,154 @@
package formatter
import (
"time"
"github.com/docker/cli/internal/licenseutils"
"github.com/docker/licensing/model"
)
const (
defaultSubscriptionsTableFormat = "table {{.Num}}\t{{.Owner}}\t{{.ProductID}}\t{{.Expires}}\t{{.ComponentsString}}"
defaultSubscriptionsQuietFormat = "{{.Num}}:{{.Summary}}"
numHeader = "NUM"
ownerHeader = "OWNER"
licenseNameHeader = "NAME"
idHeader = "ID"
dockerIDHeader = "DOCKER ID"
productIDHeader = "PRODUCT ID"
productRatePlanHeader = "PRODUCT RATE PLAN"
productRatePlanIDHeader = "PRODUCT RATE PLAN ID"
startHeader = "START"
expiresHeader = "EXPIRES"
stateHeader = "STATE"
eusaHeader = "EUSA"
pricingComponentsHeader = "PRICING COMPONENTS"
)
// NewSubscriptionsFormat returns a Format for rendering using a license Context
func NewSubscriptionsFormat(source string, quiet bool) Format {
switch source {
case TableFormatKey:
if quiet {
return defaultSubscriptionsQuietFormat
}
return defaultSubscriptionsTableFormat
case RawFormatKey:
if quiet {
return `license: {{.ID}}`
}
return `license: {{.ID}}\nname: {{.Name}}\nowner: {{.Owner}}\ncomponents: {{.ComponentsString}}\n`
}
return Format(source)
}
// SubscriptionsWrite writes the context
func SubscriptionsWrite(ctx Context, subs []licenseutils.LicenseDisplay) error {
render := func(format func(subContext subContext) error) error {
for _, sub := range subs {
licenseCtx := &licenseContext{trunc: ctx.Trunc, l: sub}
if err := format(licenseCtx); err != nil {
return err
}
}
return nil
}
licenseCtx := licenseContext{}
licenseCtx.header = map[string]string{
"Num": numHeader,
"Owner": ownerHeader,
"Name": licenseNameHeader,
"ID": idHeader,
"DockerID": dockerIDHeader,
"ProductID": productIDHeader,
"ProductRatePlan": productRatePlanHeader,
"ProductRatePlanID": productRatePlanIDHeader,
"Start": startHeader,
"Expires": expiresHeader,
"State": stateHeader,
"Eusa": eusaHeader,
"ComponentsString": pricingComponentsHeader,
}
return ctx.Write(&licenseCtx, render)
}
type licenseContext struct {
HeaderContext
trunc bool
l licenseutils.LicenseDisplay
}
func (c *licenseContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *licenseContext) Num() int {
return c.l.Num
}
func (c *licenseContext) Owner() string {
return c.l.Owner
}
func (c *licenseContext) ComponentsString() string {
return c.l.ComponentsString
}
func (c *licenseContext) Summary() string {
return c.l.String()
}
func (c *licenseContext) Name() string {
return c.l.Name
}
func (c *licenseContext) ID() string {
return c.l.ID
}
func (c *licenseContext) DockerID() string {
return c.l.DockerID
}
func (c *licenseContext) ProductID() string {
return c.l.ProductID
}
func (c *licenseContext) ProductRatePlan() string {
return c.l.ProductRatePlan
}
func (c *licenseContext) ProductRatePlanID() string {
return c.l.ProductRatePlanID
}
func (c *licenseContext) Start() *time.Time {
return c.l.Start
}
func (c *licenseContext) Expires() *time.Time {
return c.l.Expires
}
func (c *licenseContext) State() string {
return c.l.State
}
func (c *licenseContext) Eusa() *model.EusaState {
return c.l.Eusa
}
func (c *licenseContext) PricingComponents() []model.SubscriptionPricingComponent {
// Dereference the pricing component pointers in the pricing components
// so it can be rendered properly with the template formatter
var ret []model.SubscriptionPricingComponent
for _, spc := range c.l.PricingComponents {
if spc == nil {
continue
}
ret = append(ret, *spc)
}
return ret
}

View File

@ -0,0 +1,256 @@
package formatter
import (
"bytes"
"encoding/json"
"strings"
"testing"
"time"
"github.com/docker/cli/internal/licenseutils"
"github.com/docker/licensing/model"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestSubscriptionContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{Format: "{{InvalidFunction}}"},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{Format: "{{nil}}"},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table format
{
Context{Format: NewSubscriptionsFormat("table", false)},
`NUM OWNER PRODUCT ID EXPIRES PRICING COMPONENTS
1 owner1 productid1 2020-01-01 10:00:00 +0000 UTC compstring
2 owner2 productid2 2020-01-01 10:00:00 +0000 UTC compstring
`,
},
{
Context{Format: NewSubscriptionsFormat("table", true)},
`1:License Name: name1 Quantity: 10 nodes Expiration date: 2020-01-01
2:License Name: name2 Quantity: 20 nodes Expiration date: 2020-01-01
`,
},
{
Context{Format: NewSubscriptionsFormat("table {{.Owner}}", false)},
`OWNER
owner1
owner2
`,
},
{
Context{Format: NewSubscriptionsFormat("table {{.Owner}}", true)},
`OWNER
owner1
owner2
`,
},
// Raw Format
{
Context{Format: NewSubscriptionsFormat("raw", false)},
`license: id1
name: name1
owner: owner1
components: compstring
license: id2
name: name2
owner: owner2
components: compstring
`,
},
{
Context{Format: NewSubscriptionsFormat("raw", true)},
`license: id1
license: id2
`,
},
// Custom Format
{
Context{Format: NewSubscriptionsFormat("{{.Owner}}", false)},
`owner1
owner2
`,
},
}
expiration, _ := time.Parse(time.RFC822, "01 Jan 20 10:00 UTC")
for _, testcase := range cases {
subscriptions := []licenseutils.LicenseDisplay{
{
Num: 1,
Owner: "owner1",
Subscription: model.Subscription{
ID: "id1",
Name: "name1",
ProductID: "productid1",
Expires: &expiration,
PricingComponents: model.PricingComponents{
&model.SubscriptionPricingComponent{
Name: "nodes",
Value: 10,
},
},
},
ComponentsString: "compstring",
},
{
Num: 2,
Owner: "owner2",
Subscription: model.Subscription{
ID: "id2",
Name: "name2",
ProductID: "productid2",
Expires: &expiration,
PricingComponents: model.PricingComponents{
&model.SubscriptionPricingComponent{
Name: "nodes",
Value: 20,
},
},
},
ComponentsString: "compstring",
},
}
out := &bytes.Buffer{}
testcase.context.Output = out
err := SubscriptionsWrite(testcase.context, subscriptions)
if err != nil {
assert.Error(t, err, testcase.expected)
} else {
assert.Check(t, is.Equal(testcase.expected, out.String()))
}
}
}
func TestSubscriptionContextWriteJSON(t *testing.T) {
expiration, _ := time.Parse(time.RFC822, "01 Jan 20 10:00 UTC")
subscriptions := []licenseutils.LicenseDisplay{
{
Num: 1,
Owner: "owner1",
Subscription: model.Subscription{
ID: "id1",
Name: "name1",
ProductID: "productid1",
Expires: &expiration,
PricingComponents: model.PricingComponents{
&model.SubscriptionPricingComponent{
Name: "nodes",
Value: 10,
},
},
},
ComponentsString: "compstring",
},
{
Num: 2,
Owner: "owner2",
Subscription: model.Subscription{
ID: "id2",
Name: "name2",
ProductID: "productid2",
Expires: &expiration,
PricingComponents: model.PricingComponents{
&model.SubscriptionPricingComponent{
Name: "nodes",
Value: 20,
},
},
},
ComponentsString: "compstring",
},
}
expectedJSONs := []map[string]interface{}{
{
"Owner": "owner1",
"ComponentsString": "compstring",
"Expires": "2020-01-01T10:00:00Z",
"DockerID": "",
"Eusa": nil,
"ID": "id1",
"Start": nil,
"Name": "name1",
"Num": float64(1),
"PricingComponents": []interface{}{
map[string]interface{}{
"name": "nodes",
"value": float64(10),
},
},
"ProductID": "productid1",
"ProductRatePlan": "",
"ProductRatePlanID": "",
"State": "",
"Summary": "License Name: name1\tQuantity: 10 nodes\tExpiration date: 2020-01-01",
},
{
"Owner": "owner2",
"ComponentsString": "compstring",
"Expires": "2020-01-01T10:00:00Z",
"DockerID": "",
"Eusa": nil,
"ID": "id2",
"Start": nil,
"Name": "name2",
"Num": float64(2),
"PricingComponents": []interface{}{
map[string]interface{}{
"name": "nodes",
"value": float64(20),
},
},
"ProductID": "productid2",
"ProductRatePlan": "",
"ProductRatePlanID": "",
"State": "",
"Summary": "License Name: name2\tQuantity: 20 nodes\tExpiration date: 2020-01-01",
},
}
out := &bytes.Buffer{}
err := SubscriptionsWrite(Context{Format: "{{json .}}", Output: out}, subscriptions)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
var m map[string]interface{}
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Check(t, is.DeepEqual(expectedJSONs[i], m))
}
}
func TestSubscriptionContextWriteJSONField(t *testing.T) {
subscriptions := []licenseutils.LicenseDisplay{
{Num: 1, Owner: "owner1"},
{Num: 2, Owner: "owner2"},
}
out := &bytes.Buffer{}
err := SubscriptionsWrite(Context{Format: "{{json .Owner}}", Output: out}, subscriptions)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
var s string
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Check(t, is.Equal(subscriptions[i].Owner, s))
}
}

View File

@ -599,7 +599,13 @@ func (c *serviceContext) Ports() string {
pr := portRange{}
ports := []string{}
sort.Sort(byProtocolAndPublishedPort(c.service.Endpoint.Ports))
servicePorts := c.service.Endpoint.Ports
sort.Slice(servicePorts, func(i, j int) bool {
if servicePorts[i].Protocol == servicePorts[j].Protocol {
return servicePorts[i].PublishedPort < servicePorts[j].PublishedPort
}
return servicePorts[i].Protocol < servicePorts[j].Protocol
})
for _, p := range c.service.Endpoint.Ports {
if p.PublishMode == swarm.PortConfigPublishModeIngress {
@ -633,14 +639,3 @@ func (c *serviceContext) Ports() string {
}
return strings.Join(ports, ", ")
}
type byProtocolAndPublishedPort []swarm.PortConfig
func (a byProtocolAndPublishedPort) Len() int { return len(a) }
func (a byProtocolAndPublishedPort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byProtocolAndPublishedPort) Less(i, j int) bool {
if a[i].Protocol == a[j].Protocol {
return a[i].PublishedPort < a[j].PublishedPort
}
return a[i].Protocol < a[j].Protocol
}

View File

@ -133,18 +133,3 @@ func (c *signerInfoContext) Keys() string {
func (c *signerInfoContext) Signer() string {
return c.s.Name
}
// SignerInfoList helps sort []SignerInfo by signer names
type SignerInfoList []SignerInfo
func (signerInfoComp SignerInfoList) Len() int {
return len(signerInfoComp)
}
func (signerInfoComp SignerInfoList) Less(i, j int) bool {
return signerInfoComp[i].Name < signerInfoComp[j].Name
}
func (signerInfoComp SignerInfoList) Swap(i, j int) {
signerInfoComp[i], signerInfoComp[j] = signerInfoComp[j], signerInfoComp[i]
}

View File

@ -222,7 +222,7 @@ eve foobarbazquxquux, key31, key32
}
for _, testcase := range cases {
signerInfo := SignerInfoList{
signerInfo := []SignerInfo{
{Name: "alice", Keys: []string{"key11", "key12"}},
{Name: "bob", Keys: []string{"key21"}},
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},

View File

@ -0,0 +1,73 @@
package formatter
import (
clitypes "github.com/docker/cli/types"
)
const (
defaultUpdatesTableFormat = "table {{.Type}}\t{{.Version}}\t{{.Notes}}"
defaultUpdatesQuietFormat = "{{.Version}}"
updatesTypeHeader = "TYPE"
versionHeader = "VERSION"
notesHeader = "NOTES"
)
// NewUpdatesFormat returns a Format for rendering using a updates context
func NewUpdatesFormat(source string, quiet bool) Format {
switch source {
case TableFormatKey:
if quiet {
return defaultUpdatesQuietFormat
}
return defaultUpdatesTableFormat
case RawFormatKey:
if quiet {
return `update_version: {{.Version}}`
}
return `update_version: {{.Version}}\ntype: {{.Type}}\nnotes: {{.Notes}}\n`
}
return Format(source)
}
// UpdatesWrite writes the context
func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error {
render := func(format func(subContext subContext) error) error {
for _, update := range availableUpdates {
updatesCtx := &updateContext{trunc: ctx.Trunc, u: update}
if err := format(updatesCtx); err != nil {
return err
}
}
return nil
}
updatesCtx := updateContext{}
updatesCtx.header = map[string]string{
"Type": updatesTypeHeader,
"Version": versionHeader,
"Notes": notesHeader,
}
return ctx.Write(&updatesCtx, render)
}
type updateContext struct {
HeaderContext
trunc bool
u clitypes.Update
}
func (c *updateContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *updateContext) Type() string {
return c.u.Type
}
func (c *updateContext) Version() string {
return c.u.Version
}
func (c *updateContext) Notes() string {
return c.u.Notes
}

View File

@ -0,0 +1,143 @@
package formatter
import (
"bytes"
"encoding/json"
"strings"
"testing"
clitypes "github.com/docker/cli/types"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestUpdateContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{Format: "{{InvalidFunction}}"},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{Format: "{{nil}}"},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table format
{
Context{Format: NewUpdatesFormat("table", false)},
`TYPE VERSION NOTES
updateType1 version1 description 1
updateType2 version2 description 2
`,
},
{
Context{Format: NewUpdatesFormat("table", true)},
`version1
version2
`,
},
{
Context{Format: NewUpdatesFormat("table {{.Version}}", false)},
`VERSION
version1
version2
`,
},
{
Context{Format: NewUpdatesFormat("table {{.Version}}", true)},
`VERSION
version1
version2
`,
},
// Raw Format
{
Context{Format: NewUpdatesFormat("raw", false)},
`update_version: version1
type: updateType1
notes: description 1
update_version: version2
type: updateType2
notes: description 2
`,
},
{
Context{Format: NewUpdatesFormat("raw", true)},
`update_version: version1
update_version: version2
`,
},
// Custom Format
{
Context{Format: NewUpdatesFormat("{{.Version}}", false)},
`version1
version2
`,
},
}
for _, testcase := range cases {
updates := []clitypes.Update{
{Type: "updateType1", Version: "version1", Notes: "description 1"},
{Type: "updateType2", Version: "version2", Notes: "description 2"},
}
out := &bytes.Buffer{}
testcase.context.Output = out
err := UpdatesWrite(testcase.context, updates)
if err != nil {
assert.Error(t, err, testcase.expected)
} else {
assert.Check(t, is.Equal(testcase.expected, out.String()))
}
}
}
func TestUpdateContextWriteJSON(t *testing.T) {
updates := []clitypes.Update{
{Type: "updateType1", Version: "version1", Notes: "note1"},
{Type: "updateType2", Version: "version2", Notes: "note2"},
}
expectedJSONs := []map[string]interface{}{
{"Version": "version1", "Notes": "note1", "Type": "updateType1"},
{"Version": "version2", "Notes": "note2", "Type": "updateType2"},
}
out := &bytes.Buffer{}
err := UpdatesWrite(Context{Format: "{{json .}}", Output: out}, updates)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
var m map[string]interface{}
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Check(t, is.DeepEqual(expectedJSONs[i], m))
}
}
func TestUpdateContextWriteJSONField(t *testing.T) {
updates := []clitypes.Update{
{Type: "updateType1", Version: "version1"},
{Type: "updateType2", Version: "version2"},
}
out := &bytes.Buffer{}
err := UpdatesWrite(Context{Format: "{{json .Type}}", Output: out}, updates)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
var s string
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Check(t, is.Equal(updates[i].Type, s))
}
}

View File

@ -57,7 +57,7 @@ type buildOptions struct {
isolation string
quiet bool
noCache bool
console opts.NullableBool
progress string
rm bool
forceRm bool
pull bool
@ -71,6 +71,8 @@ type buildOptions struct {
stream bool
platform string
untrusted bool
secrets []string
ssh []string
}
// dockerfileFromStdin returns true when the user specified that the Dockerfile
@ -134,6 +136,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
flags.SetAnnotation("compress", "no-buildkit", nil)
flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
flags.SetAnnotation("network", "version", []string{"1.25"})
@ -151,10 +155,18 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
flags.SetAnnotation("stream", "experimental", nil)
flags.SetAnnotation("stream", "version", []string{"1.31"})
flags.SetAnnotation("stream", "no-buildkit", nil)
flags.Var(&options.console, "console", "Show console output (with buildkit only) (true, false, auto)")
flags.SetAnnotation("console", "experimental", nil)
flags.SetAnnotation("console", "version", []string{"1.38"})
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
flags.SetAnnotation("progress", "buildkit", nil)
flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret")
flags.SetAnnotation("secret", "version", []string{"1.39"})
flags.SetAnnotation("secret", "buildkit", nil)
flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|<id>[=<socket>|<key>[,<key>]])")
flags.SetAnnotation("ssh", "version", []string{"1.39"})
flags.SetAnnotation("ssh", "buildkit", nil)
return cmd
}
@ -176,14 +188,17 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
// nolint: gocyclo
func runBuild(dockerCli command.Cli, options buildOptions) error {
if os.Getenv("DOCKER_BUILDKIT") != "" {
buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
if err != nil {
return err
}
if buildkitEnabled {
return runBuildBuildKit(dockerCli, options)
}
var (
buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
err error
contextDir string
tempDir string
relDockerfile string
@ -263,15 +278,12 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
// And canonicalize dockerfile name to a platform-independent one
relDockerfile, err = archive.CanonicalTarNameForPath(relDockerfile)
if err != nil {
return errors.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
}
relDockerfile = archive.CanonicalTarNameForPath(relDockerfile)
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
ExcludePatterns: excludes,
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
ChownOpts: &idtools.Identity{UID: 0, GID: 0},
})
if err != nil {
return err
@ -338,7 +350,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
buildCtx = dockerfileCtx
}
s, err := trySession(dockerCli, contextDir)
s, err := trySession(dockerCli, contextDir, true)
if err != nil {
return err
}

View File

@ -1,29 +1,37 @@
package image
import (
"bytes"
"context"
"encoding/csv"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containerd/console"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/urlutil"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/session/filesync"
"github.com/moby/buildkit/session/secrets/secretsprovider"
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/util/progress/progressui"
"github.com/pkg/errors"
"github.com/tonistiigi/fsutil"
fsutiltypes "github.com/tonistiigi/fsutil/types"
"golang.org/x/sync/errgroup"
)
@ -35,7 +43,7 @@ var errDockerfileConflict = errors.New("ambiguous Dockerfile source: both stdin
func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
ctx := appcontext.Context()
s, err := trySession(dockerCli, options.context)
s, err := trySession(dockerCli, options.context, false)
if err != nil {
return err
}
@ -43,6 +51,13 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
return errors.Errorf("buildkit not supported by daemon")
}
if options.imageIDFile != "" {
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "removing image ID file")
}
}
var (
remote string
body io.Reader
@ -117,6 +132,20 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
}
s.Allow(authprovider.NewDockerAuthProvider())
if len(options.secrets) > 0 {
sp, err := parseSecretSpecs(options.secrets)
if err != nil {
return errors.Wrapf(err, "could not parse secrets: %v", options.secrets)
}
s.Allow(sp)
}
if len(options.ssh) > 0 {
sshp, err := parseSSHSpecs(options.ssh)
if err != nil {
return errors.Wrapf(err, "could not parse ssh: %v", options.ssh)
}
s.Allow(sshp)
}
eg, ctx := errgroup.WithContext(ctx)
@ -159,6 +188,7 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
return eg.Wait()
}
//nolint: gocyclo
func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, options buildOptions, buildOptions types.ImageBuildOptions) (finalErr error) {
response, err := dockerCli.Client().ImageBuild(context.Background(), nil, buildOptions)
if err != nil {
@ -180,17 +210,19 @@ func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, opt
t := newTracer()
ssArr := []*client.SolveStatus{}
displayStatus := func(displayCh chan *client.SolveStatus) {
if err := opts.ValidateProgressOutput(options.progress); err != nil {
return err
}
displayStatus := func(out *os.File, displayCh chan *client.SolveStatus) {
var c console.Console
out := os.Stderr
// TODO: Handle interactive output in non-interactive environment.
consoleOpt := options.console.Value()
if cons, err := console.ConsoleFromFile(out); err == nil && (consoleOpt == nil || *consoleOpt) {
// TODO: Handle tty output in non-tty environment.
if cons, err := console.ConsoleFromFile(out); err == nil && (options.progress == "auto" || options.progress == "tty") {
c = cons
}
// not using shared context to not disrupt display but let is finish reporting errors
eg.Go(func() error {
return progressui.DisplaySolveStatus(context.TODO(), c, out, displayCh)
return progressui.DisplaySolveStatus(context.TODO(), "", c, out, displayCh)
})
}
@ -210,15 +242,31 @@ func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, opt
}
close(displayCh)
}()
displayStatus(displayCh)
displayStatus(os.Stderr, displayCh)
}
return nil
})
} else {
displayStatus(t.displayCh)
displayStatus(os.Stdout, t.displayCh)
}
defer close(t.displayCh)
err = jsonmessage.DisplayJSONMessagesStream(response.Body, os.Stdout, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), t.write)
buf := bytes.NewBuffer(nil)
imageID := ""
writeAux := func(msg jsonmessage.JSONMessage) {
if msg.ID == "moby.image.id" {
var result types.BuildResult
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
fmt.Fprintf(dockerCli.Err(), "failed to parse aux message: %v", err)
}
imageID = result.ID
return
}
t.write(msg)
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buf, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), writeAux)
if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1
@ -228,10 +276,30 @@ func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, opt
return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
}
}
// Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout.
//
// TODO: we may want to use Aux messages with ID "moby.image.id" regardless of options.quiet (i.e. don't send HTTP param q=1)
// instead of assuming that output is image ID if options.quiet.
if options.quiet {
imageID = buf.String()
fmt.Fprint(dockerCli.Out(), imageID)
}
if options.imageIDFile != "" {
if imageID == "" {
return errors.Errorf("cannot write %s because server did not provide an image ID", options.imageIDFile)
}
imageID = strings.TrimSpace(imageID)
if err := ioutil.WriteFile(options.imageIDFile, []byte(imageID), 0666); err != nil {
return errors.Wrap(err, "cannot write image ID file")
}
}
return err
}
func resetUIDAndGID(s *fsutil.Stat) bool {
func resetUIDAndGID(s *fsutiltypes.Stat) bool {
s.Uid = 0
s.Gid = 0
return true
@ -298,3 +366,76 @@ func (t *tracer) write(msg jsonmessage.JSONMessage) {
t.displayCh <- &s
}
func parseSecretSpecs(sl []string) (session.Attachable, error) {
fs := make([]secretsprovider.FileSource, 0, len(sl))
for _, v := range sl {
s, err := parseSecret(v)
if err != nil {
return nil, err
}
fs = append(fs, *s)
}
store, err := secretsprovider.NewFileStore(fs)
if err != nil {
return nil, err
}
return secretsprovider.NewSecretProvider(store), nil
}
func parseSecret(value string) (*secretsprovider.FileSource, error) {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return nil, errors.Wrap(err, "failed to parse csv secret")
}
fs := secretsprovider.FileSource{}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) != 2 {
return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "type":
if value != "file" {
return nil, errors.Errorf("unsupported secret type %q", value)
}
case "id":
fs.ID = value
case "source", "src":
fs.FilePath = value
default:
return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
return &fs, nil
}
func parseSSHSpecs(sl []string) (session.Attachable, error) {
configs := make([]sshprovider.AgentConfig, 0, len(sl))
for _, v := range sl {
c, err := parseSSH(v)
if err != nil {
return nil, err
}
configs = append(configs, *c)
}
return sshprovider.NewSSHAgentProvider(configs)
}
func parseSSH(value string) (*sshprovider.AgentConfig, error) {
parts := strings.SplitN(value, "=", 2)
cfg := sshprovider.AgentConfig{
ID: parts[0],
}
if len(parts) > 1 {
cfg.Paths = strings.Split(parts[1], ",")
}
return &cfg, nil
}

View File

@ -27,13 +27,16 @@ import (
const clientSessionRemote = "client-session"
func isSessionSupported(dockerCli command.Cli) bool {
func isSessionSupported(dockerCli command.Cli, forStream bool) bool {
if !forStream && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") {
return true
}
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
}
func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) {
func trySession(dockerCli command.Cli, contextDir string, forStream bool) (*session.Session, error) {
var s *session.Session
if isSessionSupported(dockerCli) {
if isSessionSupported(dockerCli, forStream) {
sharedKey, err := getBuildSharedKey(contextDir)
if err != nil {
return nil, errors.Wrap(err, "failed to get build shared key")

View File

@ -5,6 +5,7 @@ import (
"bytes"
"compress/gzip"
"context"
"fmt"
"io"
"io/ioutil"
"os"
@ -17,6 +18,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/google/go-cmp/cmp"
"github.com/moby/buildkit/session/secrets/secretsprovider"
"gotest.tools/assert"
"gotest.tools/fs"
"gotest.tools/skip"
@ -173,6 +175,66 @@ RUN echo hello world
assert.DeepEqual(t, fakeBuild.filenames(t), []string{"Dockerfile"})
}
func TestParseSecret(t *testing.T) {
type testcase struct {
value string
errExpected bool
errMatch string
filesource *secretsprovider.FileSource
}
var testcases = []testcase{
{
value: "",
errExpected: true,
}, {
value: "foobar",
errExpected: true,
errMatch: "must be a key=value pair",
}, {
value: "foo,bar",
errExpected: true,
errMatch: "must be a key=value pair",
}, {
value: "foo=bar",
errExpected: true,
errMatch: "unexpected key",
}, {
value: "src=somefile",
filesource: &secretsprovider.FileSource{FilePath: "somefile"},
}, {
value: "source=somefile",
filesource: &secretsprovider.FileSource{FilePath: "somefile"},
}, {
value: "id=mysecret",
filesource: &secretsprovider.FileSource{ID: "mysecret"},
}, {
value: "id=mysecret,src=somefile",
filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "somefile"},
}, {
value: "id=mysecret,source=somefile,type=file",
filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "somefile"},
}, {
value: "id=mysecret,src=somefile,src=othersecretfile",
filesource: &secretsprovider.FileSource{ID: "mysecret", FilePath: "othersecretfile"},
}, {
value: "type=invalid",
errExpected: true,
errMatch: "unsupported secret type",
},
}
for _, tc := range testcases {
t.Run(tc.value, func(t *testing.T) {
secret, err := parseSecret(tc.value)
assert.Equal(t, err != nil, tc.errExpected, fmt.Sprintf("err=%v errExpected=%t", err, tc.errExpected))
if tc.errMatch != "" {
assert.ErrorContains(t, err, tc.errMatch)
}
assert.DeepEqual(t, secret, tc.filesource)
})
}
}
type fakeBuild struct {
context *tar.Reader
options types.ImageBuildOptions

View File

@ -19,6 +19,7 @@ type importOptions struct {
reference string
changes dockeropts.ListOpts
message string
platform string
}
// NewImportCommand creates a new `docker import` command
@ -43,6 +44,7 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command {
options.changes = dockeropts.NewListOpts(nil)
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
command.AddPlatformFlag(flags, &options.platform)
return cmd
}
@ -71,8 +73,9 @@ func runImport(dockerCli command.Cli, options importOptions) error {
}
importOptions := types.ImageImportOptions{
Message: options.message,
Changes: options.changes.GetAll(),
Message: options.message,
Changes: options.changes.GetAll(),
Platform: options.platform,
}
clnt := dockerCli.Client()

View File

@ -3,11 +3,14 @@ package image
import (
"context"
"fmt"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -54,8 +57,24 @@ Are you sure you want to continue?`
Are you sure you want to continue?`
)
// cloneFilter is a temporary workaround that uses existing public APIs from the filters package to clone a filter.
// TODO(tiborvass): remove this once filters.Args.Clone() is added.
func cloneFilter(args filters.Args) (newArgs filters.Args, err error) {
if args.Len() == 0 {
return filters.NewArgs(), nil
}
b, err := args.MarshalJSON()
if err != nil {
return newArgs, err
}
return filters.FromJSON(string(b))
}
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := options.filter.Value()
pruneFilters, err := cloneFilter(options.filter.Value())
if err != nil {
return 0, "", errors.Wrap(err, "could not copy filter in image prune")
}
pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all))
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
@ -73,14 +92,20 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
}
if len(report.ImagesDeleted) > 0 {
output = "Deleted Images:\n"
var sb strings.Builder
sb.WriteString("Deleted Images:\n")
for _, st := range report.ImagesDeleted {
if st.Untagged != "" {
output += fmt.Sprintln("untagged:", st.Untagged)
sb.WriteString("untagged: ")
sb.WriteString(st.Untagged)
sb.WriteByte('\n')
} else {
output += fmt.Sprintln("deleted:", st.Deleted)
sb.WriteString("deleted: ")
sb.WriteString(st.Deleted)
sb.WriteByte('\n')
}
}
output = sb.String()
spaceReclaimed = report.SpaceReclaimed
}

View File

@ -70,6 +70,14 @@ func TestNewPruneCommandSuccess(t *testing.T) {
}, nil
},
},
{
name: "label-filter",
args: []string{"--force", "--filter", "label=foobar"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Check(t, is.Equal("foobar", pruneFilter.Get("label")[0]))
return types.ImagesPruneReport{}, nil
},
},
{
name: "force-untagged",
args: []string{"--force"},

View File

@ -0,0 +1 @@
Total reclaimed space: 0B

View File

@ -6,6 +6,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -64,20 +65,23 @@ func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
}
// Update the mf
if imageManifest.Descriptor.Platform == nil {
imageManifest.Descriptor.Platform = new(ocispec.Platform)
}
if opts.os != "" {
imageManifest.Platform.OS = opts.os
imageManifest.Descriptor.Platform.OS = opts.os
}
if opts.arch != "" {
imageManifest.Platform.Architecture = opts.arch
imageManifest.Descriptor.Platform.Architecture = opts.arch
}
for _, osFeature := range opts.osFeatures {
imageManifest.Platform.OSFeatures = appendIfUnique(imageManifest.Platform.OSFeatures, osFeature)
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
}
if opts.variant != "" {
imageManifest.Platform.Variant = opts.variant
imageManifest.Descriptor.Platform.Variant = opts.variant
}
if !isValidOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture) {
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
}
return manifestStore.Save(targetRef, imgRef, imageManifest)

View File

@ -15,6 +15,7 @@ type fakeRegistryClient struct {
getManifestListFunc func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
mountBlobFunc func(ctx context.Context, source reference.Canonical, target reference.Named) error
putManifestFunc func(ctx context.Context, source reference.Named, mf distribution.Manifest) (digest.Digest, error)
getTagsFunc func(ctx context.Context, ref reference.Named) ([]string, error)
}
func (c *fakeRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
@ -45,4 +46,11 @@ func (c *fakeRegistryClient) PutManifest(ctx context.Context, ref reference.Name
return digest.Digest(""), nil
}
func (c *fakeRegistryClient) GetTags(ctx context.Context, ref reference.Named) ([]string, error) {
if c.getTagsFunc != nil {
return c.getTagsFunc(ctx, ref)
}
return nil, nil
}
var _ client.RegistryClient = &fakeRegistryClient{}

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -123,7 +124,7 @@ func printManifestList(dockerCli command.Cli, namedRef reference.Named, list []t
for _, img := range list {
mfd, err := buildManifestDescriptor(targetRepo, img)
if err != nil {
return fmt.Errorf("error assembling ManifestDescriptor")
return errors.Wrap(err, "failed to assemble ManifestDescriptor")
}
manifests = append(manifests, mfd)
}

View File

@ -14,6 +14,7 @@ import (
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
@ -50,8 +51,22 @@ func fullImageManifest(t *testing.T, ref reference.Named) types.ImageManifest {
},
})
assert.NilError(t, err)
// TODO: include image data for verbose inspect
return types.NewImageManifest(ref, digest.Digest("sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd"), types.Image{OS: "linux", Architecture: "amd64"}, man)
mt, raw, err := man.Payload()
assert.NilError(t, err)
desc := ocispec.Descriptor{
Digest: digest.FromBytes(raw),
Size: int64(len(raw)),
MediaType: mt,
Platform: &ocispec.Platform{
Architecture: "amd64",
OS: "linux",
},
}
return types.NewImageManifest(ref, desc, man)
}
func TestInspectCommandLocalManifestNotFound(t *testing.T) {

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/types"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
@ -141,7 +142,9 @@ func buildManifestList(manifests []types.ImageManifest, targetRef reference.Name
descriptors := []manifestlist.ManifestDescriptor{}
for _, imageManifest := range manifests {
if imageManifest.Platform.Architecture == "" || imageManifest.Platform.OS == "" {
if imageManifest.Descriptor.Platform == nil ||
imageManifest.Descriptor.Platform.Architecture == "" ||
imageManifest.Descriptor.Platform.OS == "" {
return nil, errors.Errorf(
"manifest %s must have an OS and Architecture to be pushed to a registry", imageManifest.Ref)
}
@ -167,17 +170,18 @@ func buildManifestDescriptor(targetRepo *registry.RepositoryInfo, imageManifest
return manifestlist.ManifestDescriptor{}, errors.Errorf("cannot use source images from a different registry than the target image: %s != %s", manifestRepoHostname, targetRepoHostname)
}
mediaType, raw, err := imageManifest.Payload()
if err != nil {
return manifestlist.ManifestDescriptor{}, err
manifest := manifestlist.ManifestDescriptor{
Descriptor: distribution.Descriptor{
Digest: imageManifest.Descriptor.Digest,
Size: imageManifest.Descriptor.Size,
MediaType: imageManifest.Descriptor.MediaType,
},
}
manifest := manifestlist.ManifestDescriptor{
Platform: imageManifest.Platform,
platform := types.PlatformSpecFromOCI(imageManifest.Descriptor.Platform)
if platform != nil {
manifest.Platform = *platform
}
manifest.Descriptor.Digest = imageManifest.Digest
manifest.Size = int64(len(raw))
manifest.MediaType = mediaType
if err = manifest.Descriptor.Digest.Validate(); err != nil {
return manifestlist.ManifestDescriptor{}, errors.Wrapf(err,
@ -195,7 +199,11 @@ func buildBlobRequestList(imageManifest types.ImageManifest, repoName reference.
if err != nil {
return nil, err
}
blobReqs = append(blobReqs, manifestBlob{canonical: canonical, os: imageManifest.Platform.OS})
var os string
if imageManifest.Descriptor.Platform != nil {
os = imageManifest.Descriptor.Platform.OS
}
blobReqs = append(blobReqs, manifestBlob{canonical: canonical, os: os})
}
return blobReqs, nil
}
@ -206,7 +214,7 @@ func buildPutManifestRequest(imageManifest types.ImageManifest, targetRef refere
if err != nil {
return mountRequest{}, err
}
mountRef, err := reference.WithDigest(refWithoutTag, imageManifest.Digest)
mountRef, err := reference.WithDigest(refWithoutTag, imageManifest.Descriptor.Digest)
if err != nil {
return mountRequest{}, err
}

View File

@ -1,6 +1,18 @@
{
"Ref": "example.com/alpine:3.0",
"Digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"Descriptor": {
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"digest": "sha256:1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe",
"size": 528,
"platform": {
"architecture": "arm",
"os": "freebsd",
"os.features": [
"feature1"
],
"variant": "v7"
}
},
"SchemaV2Manifest": {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
@ -16,13 +28,5 @@
"digest": "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926"
}
]
},
"Platform": {
"architecture": "arm",
"os": "freebsd",
"os.features": [
"feature1"
],
"variant": "v7"
}
}

View File

@ -4,8 +4,8 @@
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"size": 528,
"digest": "sha256:1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe",
"platform": {
"architecture": "amd64",
"os": "linux"
@ -13,8 +13,8 @@
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"size": 528,
"digest": "sha256:1072e499f3f655a032e88542330cf75b02e7bdf673278f701d7ba61629ee3ebe",
"platform": {
"architecture": "amd64",
"os": "linux"

View File

@ -18,6 +18,7 @@ type osArch struct {
// list of valid os/arch values (see "Optional Environment Variables" section
// of https://golang.org/doc/install/source
// Added linux/s390x as we know System z support already exists
// Keep in sync with _docker_manifest_annotate in contrib/completion/bash/docker
var validOSArches = map[osArch]bool{
{os: "darwin", arch: "386"}: true,
{os: "darwin", arch: "amd64"}: true,

View File

@ -10,14 +10,9 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
"vbom.ml/util/sortorder"
)
type byNetworkName []types.NetworkResource
func (r byNetworkName) Len() int { return len(r) }
func (r byNetworkName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byNetworkName) Less(i, j int) bool { return r[i].Name < r[j].Name }
type listOptions struct {
quiet bool
noTrunc bool
@ -64,7 +59,9 @@ func runList(dockerCli command.Cli, options listOptions) error {
}
}
sort.Sort(byNetworkName(networkResources))
sort.Slice(networkResources, func(i, j int) bool {
return sortorder.NaturalLess(networkResources[i].Name, networkResources[j].Name)
})
networksCtx := formatter.Context{
Output: dockerCli.Out(),

View File

@ -3,7 +3,6 @@ package network
import (
"context"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/internal/test"
@ -41,23 +40,55 @@ func TestNetworkListErrors(t *testing.T) {
}
}
func TestNetworkListWithFlags(t *testing.T) {
expectedOpts := types.NetworkListOptions{
Filters: filters.NewArgs(filters.Arg("image.name", "ubuntu")),
func TestNetworkList(t *testing.T) {
testCases := []struct {
doc string
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
flags map[string]string
golden string
}{
{
doc: "network list with flags",
flags: map[string]string{
"filter": "image.name=ubuntu",
},
golden: "network-list.golden",
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
expectedOpts := types.NetworkListOptions{
Filters: filters.NewArgs(filters.Arg("image.name", "ubuntu")),
}
assert.Check(t, is.DeepEqual(expectedOpts, options, cmp.AllowUnexported(filters.Args{})))
return []types.NetworkResource{*NetworkResource(NetworkResourceID("123454321"),
NetworkResourceName("network_1"),
NetworkResourceDriver("09.7.01"),
NetworkResourceScope("global"))}, nil
},
},
{
doc: "network list sort order",
flags: map[string]string{
"format": "{{ .Name }}",
},
golden: "network-list-sort.golden",
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
return []types.NetworkResource{
*NetworkResource(NetworkResourceName("network-2-foo")),
*NetworkResource(NetworkResourceName("network-1-foo")),
*NetworkResource(NetworkResourceName("network-10-foo"))}, nil
},
},
}
cli := test.NewFakeCli(&fakeClient{
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
assert.Check(t, is.DeepEqual(expectedOpts, options, cmp.AllowUnexported(filters.Args{})))
return []types.NetworkResource{*NetworkResource(NetworkResourceID("123454321"),
NetworkResourceName("network_1"),
NetworkResourceDriver("09.7.01"),
NetworkResourceScope("global"))}, nil
},
})
cmd := newListCommand(cli)
cmd.Flags().Set("filter", "image.name=ubuntu")
assert.NilError(t, cmd.Execute())
golden.Assert(t, strings.TrimSpace(cli.OutBuffer().String()), "network-list.golden")
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc})
cmd := newListCommand(cli)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), tc.golden)
})
}
}

View File

@ -70,7 +70,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e
// RunPrune calls the Network Prune API
// This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter})
return 0, output, err
}

View File

@ -0,0 +1,3 @@
network-1-foo
network-2-foo
network-10-foo

View File

@ -1,2 +1,2 @@
NETWORK ID NAME DRIVER SCOPE
123454321 network_1 09.7.01 global
123454321 network_1 09.7.01 global

View File

@ -9,19 +9,10 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"vbom.ml/util/sortorder"
)
type byHostname []swarm.Node
func (n byHostname) Len() int { return len(n) }
func (n byHostname) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byHostname) Less(i, j int) bool {
return sortorder.NaturalLess(n[i].Description.Hostname, n[j].Description.Hostname)
}
type listOptions struct {
quiet bool
format string
@ -80,6 +71,8 @@ func runList(dockerCli command.Cli, options listOptions) error {
Output: dockerCli.Out(),
Format: formatter.NewNodeFormat(format, options.quiet),
}
sort.Sort(byHostname(nodes))
sort.Slice(nodes, func(i, j int) bool {
return sortorder.NaturalLess(nodes[i].Description.Hostname, nodes[j].Description.Hostname)
})
return formatter.NodeWrite(nodesCtx, nodes, info)
}

View File

@ -5,6 +5,7 @@ import (
"io"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
)
@ -14,6 +15,8 @@ type fakeClient struct {
pluginDisableFunc func(name string, disableOptions types.PluginDisableOptions) error
pluginEnableFunc func(name string, options types.PluginEnableOptions) error
pluginRemoveFunc func(name string, options types.PluginRemoveOptions) error
pluginInstallFunc func(name string, options types.PluginInstallOptions) (io.ReadCloser, error)
pluginListFunc func(filter filters.Args) (types.PluginsListResponse, error)
}
func (c *fakeClient) PluginCreate(ctx context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
@ -43,3 +46,18 @@ func (c *fakeClient) PluginRemove(context context.Context, name string, removeOp
}
return nil
}
func (c *fakeClient) PluginInstall(context context.Context, name string, installOptions types.PluginInstallOptions) (io.ReadCloser, error) {
if c.pluginInstallFunc != nil {
return c.pluginInstallFunc(name, installOptions)
}
return nil, nil
}
func (c *fakeClient) PluginList(context context.Context, filter filters.Args) (types.PluginsListResponse, error) {
if c.pluginListFunc != nil {
return c.pluginListFunc(filter)
}
return types.PluginsListResponse{}, nil
}

View File

@ -151,7 +151,7 @@ func runInstall(dockerCli command.Cli, opts pluginOptions) error {
responseBody, err := dockerCli.Client().PluginInstall(ctx, localName, options)
if err != nil {
if strings.Contains(err.Error(), "(image) when fetching") {
return errors.New(err.Error() + " - Use `docker image pull`")
return errors.New(err.Error() + " - Use \"docker image pull\"")
}
return err
}

View File

@ -0,0 +1,141 @@
package plugin
import (
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/docker/docker/api/types"
"gotest.tools/assert"
)
func TestInstallErrors(t *testing.T) {
testCases := []struct {
description string
args []string
expectedError string
installFunc func(name string, options types.PluginInstallOptions) (io.ReadCloser, error)
}{
{
description: "insufficient number of arguments",
args: []string{},
expectedError: "requires at least 1 argument",
},
{
description: "invalid alias",
args: []string{"foo", "--alias", "UPPERCASE_ALIAS"},
expectedError: "invalid",
},
{
description: "invalid plugin name",
args: []string{"UPPERCASE_REPONAME"},
expectedError: "invalid",
},
{
description: "installation error",
args: []string{"foo"},
expectedError: "Error installing plugin",
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return nil, fmt.Errorf("Error installing plugin")
},
},
{
description: "installation error due to missing image",
args: []string{"foo"},
expectedError: "docker image pull",
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return nil, fmt.Errorf("(image) when fetching")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
cmd := newInstallCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestInstallContentTrustErrors(t *testing.T) {
testCases := []struct {
description string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
description: "install plugin, offline notary server",
args: []string{"plugin:tag"},
expectedError: "client is offline",
notaryFunc: notary.GetOfflineNotaryRepository,
},
{
description: "install plugin, uninitialized notary server",
args: []string{"plugin:tag"},
expectedError: "remote trust data does not exist",
notaryFunc: notary.GetUninitializedNotaryRepository,
},
{
description: "install plugin, empty notary server",
args: []string{"plugin:tag"},
expectedError: "No valid trust data for tag",
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return nil, fmt.Errorf("should not try to install plugin")
},
}, test.EnableContentTrust)
cli.SetNotaryClient(tc.notaryFunc)
cmd := newInstallCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestInstall(t *testing.T) {
testCases := []struct {
description string
args []string
expectedOutput string
installFunc func(name string, options types.PluginInstallOptions) (io.ReadCloser, error)
}{
{
description: "install with no additional flags",
args: []string{"foo"},
expectedOutput: "Installed plugin foo\n",
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
{
description: "install with disable flag",
args: []string{"--disable", "foo"},
expectedOutput: "Installed plugin foo\n",
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
assert.Check(t, options.Disabled)
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
cmd := newInstallCommand(cli)
cmd.SetArgs(tc.args)
assert.NilError(t, cmd.Execute())
assert.Check(t, strings.Contains(cli.OutBuffer().String(), tc.expectedOutput))
}
}

View File

@ -2,12 +2,14 @@ package plugin
import (
"context"
"sort"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
"vbom.ml/util/sortorder"
)
type listOptions struct {
@ -46,6 +48,10 @@ func runList(dockerCli command.Cli, options listOptions) error {
return err
}
sort.Slice(plugins, func(i, j int) bool {
return sortorder.NaturalLess(plugins[i].Name, plugins[j].Name)
})
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !options.quiet {

View File

@ -0,0 +1,174 @@
package plugin
import (
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
"gotest.tools/golden"
)
func TestListErrors(t *testing.T) {
testCases := []struct {
description string
args []string
flags map[string]string
expectedError string
listFunc func(filter filters.Args) (types.PluginsListResponse, error)
}{
{
description: "too many arguments",
args: []string{"foo"},
expectedError: "accepts no arguments",
},
{
description: "error listing plugins",
args: []string{},
expectedError: "error listing plugins",
listFunc: func(filter filters.Args) (types.PluginsListResponse, error) {
return types.PluginsListResponse{}, fmt.Errorf("error listing plugins")
},
},
{
description: "invalid format",
args: []string{},
flags: map[string]string{
"format": "{{invalid format}}",
},
expectedError: "Template parsing error",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
cmd := newListCommand(cli)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestList(t *testing.T) {
singlePluginListFunc := func(filter filters.Args) (types.PluginsListResponse, error) {
return types.PluginsListResponse{
{
ID: "id-foo",
Name: "name-foo",
Enabled: true,
Config: types.PluginConfig{
Description: "desc-bar",
},
},
}, nil
}
testCases := []struct {
description string
args []string
flags map[string]string
golden string
listFunc func(filter filters.Args) (types.PluginsListResponse, error)
}{
{
description: "list with no additional flags",
args: []string{},
golden: "plugin-list-without-format.golden",
listFunc: singlePluginListFunc,
},
{
description: "list with filters",
args: []string{},
flags: map[string]string{
"filter": "foo=bar",
},
golden: "plugin-list-without-format.golden",
listFunc: func(filter filters.Args) (types.PluginsListResponse, error) {
assert.Check(t, is.Equal("bar", filter.Get("foo")[0]))
return singlePluginListFunc(filter)
},
},
{
description: "list with quiet option",
args: []string{},
flags: map[string]string{
"quiet": "true",
},
golden: "plugin-list-with-quiet-option.golden",
listFunc: singlePluginListFunc,
},
{
description: "list with no-trunc option",
args: []string{},
flags: map[string]string{
"no-trunc": "true",
"format": "{{ .ID }}",
},
golden: "plugin-list-with-no-trunc-option.golden",
listFunc: func(filter filters.Args) (types.PluginsListResponse, error) {
return types.PluginsListResponse{
{
ID: "xyg4z2hiSLO5yTnBJfg4OYia9gKA6Qjd",
Name: "name-foo",
Enabled: true,
Config: types.PluginConfig{
Description: "desc-bar",
},
},
}, nil
},
},
{
description: "list with format",
args: []string{},
flags: map[string]string{
"format": "{{ .Name }}",
},
golden: "plugin-list-with-format.golden",
listFunc: singlePluginListFunc,
},
{
description: "list output is sorted based on plugin name",
args: []string{},
flags: map[string]string{
"format": "{{ .Name }}",
},
golden: "plugin-list-sort.golden",
listFunc: func(filter filters.Args) (types.PluginsListResponse, error) {
return types.PluginsListResponse{
{
ID: "id-1",
Name: "plugin-1-foo",
},
{
ID: "id-2",
Name: "plugin-10-foo",
},
{
ID: "id-3",
Name: "plugin-2-foo",
},
}, nil
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
cmd := newListCommand(cli)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), tc.golden)
}
}

View File

@ -0,0 +1,3 @@
plugin-1-foo
plugin-2-foo
plugin-10-foo

View File

@ -0,0 +1 @@
name-foo

View File

@ -0,0 +1 @@
xyg4z2hiSLO5yTnBJfg4OYia9gKA6Qjd

View File

@ -0,0 +1 @@
id-foo

View File

@ -0,0 +1,2 @@
ID NAME DESCRIPTION ENABLED
id-foo name-foo desc-bar true

View File

@ -11,6 +11,7 @@ import (
"runtime"
"strings"
"github.com/docker/cli/cli/debug"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
@ -26,9 +27,10 @@ func ElectAuthServer(ctx context.Context, cli Cli) string {
// example a Linux client might be interacting with a Windows daemon, hence
// the default registry URL might be Windows specific.
serverAddress := registry.IndexServer
if info, err := cli.Client().Info(ctx); err != nil {
if info, err := cli.Client().Info(ctx); err != nil && debug.IsEnabled() {
// Only report the warning if we're in debug mode to prevent nagging during engine initialization workflows
fmt.Fprintf(cli.Err(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
} else if info.IndexServerAddress == "" {
} else if info.IndexServerAddress == "" && debug.IsEnabled() {
fmt.Fprintf(cli.Err(), "Warning: Empty registry endpoint from daemon. Using system default: %s\n", serverAddress)
} else {
serverAddress = info.IndexServerAddress

View File

@ -125,6 +125,11 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error { //nolint: gocycl
}
response, err = clnt.RegistryLogin(ctx, *authConfig)
if err != nil && client.IsErrConnectionFailed(err) {
// If the server isn't responding (yet) attempt to login purely client side
response, err = loginClientSide(ctx, *authConfig)
}
// If we (still) have an error, give up
if err != nil {
return err
}
@ -167,3 +172,17 @@ func loginWithCredStoreCreds(ctx context.Context, dockerCli command.Cli, authCon
}
return response, err
}
func loginClientSide(ctx context.Context, auth types.AuthConfig) (registrytypes.AuthenticateOKBody, error) {
svc, err := registry.NewService(registry.ServiceOptions{})
if err != nil {
return registrytypes.AuthenticateOKBody{}, err
}
status, token, err := svc.Auth(ctx, &auth, command.UserAgent())
return registrytypes.AuthenticateOKBody{
Status: status,
IdentityToken: token,
}, err
}

View File

@ -28,7 +28,6 @@ type fakeClient struct {
client.Client
}
// nolint: unparam
func (c fakeClient) RegistryLogin(ctx context.Context, auth types.AuthConfig) (registrytypes.AuthenticateOKBody, error) {
if auth.Password == expiredPassword {
return registrytypes.AuthenticateOKBody{}, fmt.Errorf("Invalid Username or Password")

View File

@ -9,7 +9,6 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
)
@ -81,13 +80,14 @@ func runSearch(dockerCli command.Cli, options searchOptions) error {
clnt := dockerCli.Client()
unorderedResults, err := clnt.ImageSearch(ctx, options.term, searchOptions)
results, err := clnt.ImageSearch(ctx, options.term, searchOptions)
if err != nil {
return err
}
results := searchResultsByStars(unorderedResults)
sort.Sort(results)
sort.Slice(results, func(i, j int) bool {
return results[j].StarCount < results[i].StarCount
})
searchCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewSearchFormat(options.format),
@ -95,10 +95,3 @@ func runSearch(dockerCli command.Cli, options searchOptions) error {
}
return formatter.SearchWrite(searchCtx, results, options.automated, int(options.stars))
}
// searchResultsByStars sorts search results in descending order by number of stars.
type searchResultsByStars []registrytypes.SearchResult
func (r searchResultsByStars) Len() int { return len(r) }
func (r searchResultsByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r searchResultsByStars) Less(i, j int) bool { return r[j].StarCount < r[i].StarCount }

View File

@ -13,6 +13,7 @@ import (
// Prevents a circular import with "github.com/docker/cli/internal/test"
. "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/debug"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
@ -78,6 +79,8 @@ func TestElectAuthServer(t *testing.T) {
},
},
}
// Enable debug to see warnings we're checking for
debug.Enable()
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{infoFunc: tc.infoFunc})
server := ElectAuthServer(context.Background(), cli)

View File

@ -9,19 +9,10 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"vbom.ml/util/sortorder"
)
type bySecretName []swarm.Secret
func (r bySecretName) Len() int { return len(r) }
func (r bySecretName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r bySecretName) Less(i, j int) bool {
return sortorder.NaturalLess(r[i].Spec.Name, r[j].Spec.Name)
}
type listOptions struct {
quiet bool
format string
@ -66,7 +57,9 @@ func runSecretList(dockerCli command.Cli, options listOptions) error {
}
}
sort.Sort(bySecretName(secrets))
sort.Slice(secrets, func(i, j int) bool {
return sortorder.NaturalLess(secrets[i].Spec.Name, secrets[j].Spec.Name)
})
secretCtx := formatter.Context{
Output: dockerCli.Out(),

View File

@ -44,12 +44,6 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
type byName []swarm.Service
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Spec.Name, n[j].Spec.Name) }
func runList(dockerCli command.Cli, options listOptions) error {
ctx := context.Background()
client := dockerCli.Client()
@ -60,7 +54,9 @@ func runList(dockerCli command.Cli, options listOptions) error {
return err
}
sort.Sort(byName(services))
sort.Slice(services, func(i, j int) bool {
return sortorder.NaturalLess(services[i].Spec.Name, services[j].Spec.Name)
})
info := map[string]formatter.ServiceListInfo{}
if len(services) > 0 && !options.quiet {
// only non-empty services and not quiet, should we call TaskList and NodeList api

View File

@ -598,7 +598,9 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
}
networks[i].Target = nwID
}
sort.Sort(byNetworkTarget(networks))
sort.Slice(networks, func(i, j int) bool {
return networks[i].Target < networks[j].Target
})
resources, err := options.resources.ToResourceRequirements()
if err != nil {

View File

@ -140,7 +140,7 @@ loop:
}
func updateNodeFilter(ctx context.Context, client client.APIClient, filter filters.Args) error {
if filter.Include("node") {
if filter.Contains("node") {
nodeFilters := filter.Get("node")
for _, nodeFilter := range nodeFilters {
nodeReference, err := node.Reference(ctx, client, nodeFilter)

View File

@ -302,6 +302,12 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
if task.Resources == nil {
task.Resources = &swarm.ResourceRequirements{}
}
if task.Resources.Limits == nil {
task.Resources.Limits = &swarm.Resources{}
}
if task.Resources.Reservations == nil {
task.Resources.Reservations = &swarm.Resources{}
}
return task.Resources
}
@ -753,20 +759,6 @@ func removeItems(
return newSeq
}
type byMountSource []mounttypes.Mount
func (m byMountSource) Len() int { return len(m) }
func (m byMountSource) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m byMountSource) Less(i, j int) bool {
a, b := m[i], m[j]
if a.Source == b.Source {
return a.Target < b.Target
}
return a.Source < b.Source
}
func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
mountsByTarget := map[string]mounttypes.Mount{}
@ -796,7 +788,15 @@ func updateMounts(flags *pflag.FlagSet, mounts *[]mounttypes.Mount) error {
newMounts = append(newMounts, mount)
}
}
sort.Sort(byMountSource(newMounts))
sort.Slice(newMounts, func(i, j int) bool {
a, b := newMounts[i], newMounts[j]
if a.Source == b.Source {
return a.Target < b.Target
}
return a.Source < b.Source
})
*mounts = newMounts
return nil
}
@ -886,16 +886,6 @@ func updateDNSConfig(flags *pflag.FlagSet, config **swarm.DNSConfig) error {
return nil
}
type byPortConfig []swarm.PortConfig
func (r byPortConfig) Len() int { return len(r) }
func (r byPortConfig) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byPortConfig) Less(i, j int) bool {
// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
// In updatePorts we already filter out with map so there is duplicate entries
return portConfigToString(&r[i]) < portConfigToString(&r[j])
}
func portConfigToString(portConfig *swarm.PortConfig) string {
protocol := portConfig.Protocol
mode := portConfig.PublishMode
@ -944,7 +934,11 @@ portLoop:
}
// Sort the PortConfig to avoid unnecessary updates
sort.Sort(byPortConfig(newPorts))
sort.Slice(newPorts, func(i, j int) bool {
// We convert PortConfig into `port/protocol`, e.g., `80/tcp`
// In updatePorts we already filter out with map so there is duplicate entries
return portConfigToString(&newPorts[i]) < portConfigToString(&newPorts[j])
})
*portConfig = newPorts
return nil
}
@ -1142,14 +1136,6 @@ func updateHealthcheck(flags *pflag.FlagSet, containerSpec *swarm.ContainerSpec)
return nil
}
type byNetworkTarget []swarm.NetworkAttachmentConfig
func (m byNetworkTarget) Len() int { return len(m) }
func (m byNetworkTarget) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m byNetworkTarget) Less(i, j int) bool {
return m[i].Target < m[j].Target
}
func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flags *pflag.FlagSet, spec *swarm.ServiceSpec) error {
// spec.TaskTemplate.Networks takes precedence over the deprecated
// spec.Networks field. If spec.Network is in use, we'll migrate those
@ -1198,7 +1184,9 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag
}
}
sort.Sort(byNetworkTarget(newNetworks))
sort.Slice(newNetworks, func(i, j int) bool {
return newNetworks[i].Target < newNetworks[j].Target
})
spec.TaskTemplate.Networks = newNetworks
return nil

View File

@ -617,6 +617,38 @@ func TestUpdateIsolationValid(t *testing.T) {
// and that values are not updated are not reset to their default value
func TestUpdateLimitsReservations(t *testing.T) {
spec := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
},
}
// test that updating works if the service did not previously
// have limits set (https://github.com/moby/moby/issues/38363)
flags := newUpdateCommand(nil).Flags()
err := flags.Set(flagLimitCPU, "2")
assert.NilError(t, err)
err = flags.Set(flagLimitMemory, "200M")
assert.NilError(t, err)
err = updateService(context.Background(), nil, flags, &spec)
assert.NilError(t, err)
spec = swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
},
}
// test that updating works if the service did not previously
// have reservations set (https://github.com/moby/moby/issues/38363)
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagReserveCPU, "2")
assert.NilError(t, err)
err = flags.Set(flagReserveMemory, "200M")
assert.NilError(t, err)
err = updateService(context.Background(), nil, flags, &spec)
assert.NilError(t, err)
spec = swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
Resources: &swarm.ResourceRequirements{
@ -632,8 +664,8 @@ func TestUpdateLimitsReservations(t *testing.T) {
},
}
flags := newUpdateCommand(nil).Flags()
err := flags.Set(flagLimitCPU, "2")
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagLimitCPU, "2")
assert.NilError(t, err)
err = flags.Set(flagReserveCPU, "2")
assert.NilError(t, err)

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -27,7 +28,11 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
Short: "Manage Docker stacks",
Args: cli.NoArgs,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
orchestrator, err := getOrchestrator(dockerCli.ConfigFile(), cmd, dockerCli.Err())
configFile := dockerCli.ConfigFile()
if configFile == nil {
configFile = cliconfig.LoadDefaultConfigFile(dockerCli.Err())
}
orchestrator, err := getOrchestrator(configFile, cmd, dockerCli.Err())
if err != nil {
return err
}
@ -42,9 +47,13 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
},
}
defaultHelpFunc := cmd.HelpFunc()
cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) {
hideOrchestrationFlags(cmd, opts.orchestrator)
defaultHelpFunc(cmd, args)
cmd.SetHelpFunc(func(c *cobra.Command, args []string) {
if err := cmd.PersistentPreRunE(c, args); err != nil {
fmt.Fprintln(dockerCli.Err(), err)
return
}
hideOrchestrationFlags(c, opts.orchestrator)
defaultHelpFunc(c, args)
})
cmd.AddCommand(
newDeployCommand(dockerCli, &opts),

View File

@ -58,7 +58,7 @@ func newDeployCommand(dockerCli command.Cli, common *commonOptions) *cobra.Comma
flags.StringVar(&opts.Bundlefile, "bundle-file", "", "Path to a Distributed Application Bundle file")
flags.SetAnnotation("bundle-file", "experimental", nil)
flags.SetAnnotation("bundle-file", "swarm", nil)
flags.StringSliceVarP(&opts.Composefiles, "compose-file", "c", []string{}, "Path to a Compose file")
flags.StringSliceVarP(&opts.Composefiles, "compose-file", "c", []string{}, `Path to a Compose file, or "-" to read from stdin`)
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
flags.BoolVar(&opts.SendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.SetAnnotation("with-registry-auth", "swarm", nil)
@ -81,7 +81,7 @@ func RunDeploy(dockerCli command.Cli, flags *pflag.FlagSet, config *composetypes
case commonOrchestrator.HasKubernetes():
kli, err := kubernetes.WrapCli(dockerCli, kubernetes.NewOptions(flags, commonOrchestrator))
if err != nil {
return err
return errors.Wrap(err, "unable to deploy to Kubernetes")
}
return kubernetes.RunDeploy(kli, opts, config)
default:

View File

@ -4,10 +4,12 @@ import (
"fmt"
"net"
"net/url"
"os"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/kubernetes"
cliv1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
"github.com/pkg/errors"
flag "github.com/spf13/pflag"
kubeclient "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
@ -58,7 +60,10 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
cli.kubeNamespace = opts.Namespace
if opts.Namespace == "" {
configNamespace, _, err := clientConfig.Namespace()
if err != nil {
switch {
case os.IsNotExist(err), os.IsPermission(err):
return nil, errors.Wrap(err, "unable to load configuration file")
case err != nil:
return nil, err
}
cli.kubeNamespace = configNamespace

View File

@ -8,13 +8,72 @@ import (
"strings"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/schema"
composeTypes "github.com/docker/cli/cli/compose/types"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/docker/cli/kubernetes/compose/v1beta2"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NewStackConverter returns a converter from types.Config (compose) to the specified
// stack version or error out if the version is not supported or existent.
func NewStackConverter(version string) (StackConverter, error) {
switch version {
case "v1beta1":
return stackV1Beta1Converter{}, nil
case "v1beta2":
return stackV1Beta2Converter{}, nil
default:
return nil, errors.Errorf("stack version %s unsupported", version)
}
}
// StackConverter converts a compose types.Config to a Stack
type StackConverter interface {
FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error)
}
type stackV1Beta1Converter struct{}
func (s stackV1Beta1Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
cfg.Version = v1beta1.MaxComposeVersion
st, err := fromCompose(stderr, name, cfg)
if err != nil {
return Stack{}, err
}
res, err := yaml.Marshal(cfg)
if err != nil {
return Stack{}, err
}
// reload the result to check that it produced a valid 3.5 compose file
resparsedConfig, err := loader.ParseYAML(res)
if err != nil {
return Stack{}, err
}
if err = schema.Validate(resparsedConfig, v1beta1.MaxComposeVersion); err != nil {
return Stack{}, errors.Wrapf(err, "the compose yaml file is invalid with v%s", v1beta1.MaxComposeVersion)
}
st.ComposeFile = string(res)
return st, nil
}
type stackV1Beta2Converter struct{}
func (s stackV1Beta2Converter) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return fromCompose(stderr, name, cfg)
}
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (Stack, error) {
return Stack{
Name: name,
Spec: fromComposeConfig(stderr, cfg),
}, nil
}
func loadStackData(composefile string) (*composetypes.Config, error) {
parsed, err := loader.ParseYAML([]byte(composefile))
if err != nil {
@ -30,44 +89,44 @@ func loadStackData(composefile string) (*composetypes.Config, error) {
}
// Conversions from internal stack to different stack compose component versions.
func stackFromV1beta1(in *v1beta1.Stack) (stack, error) {
func stackFromV1beta1(in *v1beta1.Stack) (Stack, error) {
cfg, err := loadStackData(in.Spec.ComposeFile)
if err != nil {
return stack{}, err
return Stack{}, err
}
return stack{
name: in.ObjectMeta.Name,
namespace: in.ObjectMeta.Namespace,
composeFile: in.Spec.ComposeFile,
spec: fromComposeConfig(ioutil.Discard, cfg),
return Stack{
Name: in.ObjectMeta.Name,
Namespace: in.ObjectMeta.Namespace,
ComposeFile: in.Spec.ComposeFile,
Spec: fromComposeConfig(ioutil.Discard, cfg),
}, nil
}
func stackToV1beta1(s stack) *v1beta1.Stack {
func stackToV1beta1(s Stack) *v1beta1.Stack {
return &v1beta1.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Name: s.Name,
},
Spec: v1beta1.StackSpec{
ComposeFile: s.composeFile,
ComposeFile: s.ComposeFile,
},
}
}
func stackFromV1beta2(in *v1beta2.Stack) stack {
return stack{
name: in.ObjectMeta.Name,
namespace: in.ObjectMeta.Namespace,
spec: in.Spec,
func stackFromV1beta2(in *v1beta2.Stack) Stack {
return Stack{
Name: in.ObjectMeta.Name,
Namespace: in.ObjectMeta.Namespace,
Spec: in.Spec,
}
}
func stackToV1beta2(s stack) *v1beta2.Stack {
func stackToV1beta2(s Stack) *v1beta2.Stack {
return &v1beta2.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: s.name,
Name: s.Name,
},
Spec: s.spec,
Spec: s.Spec,
}
}

View File

@ -0,0 +1,18 @@
package kubernetes
import (
"testing"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
)
func TestNewStackConverter(t *testing.T) {
_, err := NewStackConverter("v1alpha1")
assert.Check(t, is.ErrorContains(err, "stack version v1alpha1 unsupported"))
_, err = NewStackConverter("v1beta1")
assert.NilError(t, err)
_, err = NewStackConverter("v1beta2")
assert.NilError(t, err)
}

View File

@ -8,16 +8,11 @@ import (
"github.com/docker/cli/cli/command/stack/options"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/morikuni/aec"
"github.com/pkg/errors"
)
// RunDeploy is the kubernetes implementation of docker stack deploy
func RunDeploy(dockerCli *KubeCli, opts options.Deploy, cfg *composetypes.Config) error {
cmdOut := dockerCli.Out()
// Check arguments
if len(opts.Composefiles) == 0 {
return errors.Errorf("Please specify only one compose file (with --compose-file).")
}
// Initialize clients
composeClient, err := dockerCli.composeClient()
@ -75,13 +70,13 @@ func RunDeploy(dockerCli *KubeCli, opts options.Deploy, cfg *composetypes.Config
}
}()
err = watcher.Watch(stack.name, stack.getServices(), statusUpdates)
err = watcher.Watch(stack.Name, stack.getServices(), statusUpdates)
close(statusUpdates)
<-displayDone
if err != nil {
return err
}
fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.name)
fmt.Fprintf(cmdOut, "\nStack %s is stable and running\n\n", stack.Name)
return nil
}

View File

@ -48,10 +48,10 @@ func getStacks(kubeCli *KubeCli, opts options.List) ([]*formatter.Stack, error)
var formattedStacks []*formatter.Stack
for _, stack := range stacks {
formattedStacks = append(formattedStacks, &formatter.Stack{
Name: stack.name,
Name: stack.Name,
Services: len(stack.getServices()),
Orchestrator: "Kubernetes",
Namespace: stack.namespace,
Namespace: stack.Namespace,
})
}
return formattedStacks, nil

View File

@ -12,18 +12,18 @@ import (
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// stack is the main type used by stack commands so they remain independent from kubernetes compose component version.
type stack struct {
name string
namespace string
composeFile string
spec *v1beta2.StackSpec
// Stack is the main type used by stack commands so they remain independent from kubernetes compose component version.
type Stack struct {
Name string
Namespace string
ComposeFile string
Spec *v1beta2.StackSpec
}
// getServices returns all the stack service names, sorted lexicographically
func (s *stack) getServices() []string {
services := make([]string, len(s.spec.Services))
for i, service := range s.spec.Services {
func (s *Stack) getServices() []string {
services := make([]string, len(s.Spec.Services))
for i, service := range s.Spec.Services {
services[i] = service.Name
}
sort.Strings(services)
@ -31,8 +31,8 @@ func (s *stack) getServices() []string {
}
// createFileBasedConfigMaps creates a Kubernetes ConfigMap for each Compose global file-based config.
func (s *stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface) error {
for name, config := range s.spec.Configs {
func (s *Stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface) error {
for name, config := range s.Spec.Configs {
if config.File == "" {
continue
}
@ -43,7 +43,7 @@ func (s *stack) createFileBasedConfigMaps(configMaps corev1.ConfigMapInterface)
return err
}
if _, err := configMaps.Create(toConfigMap(s.name, name, fileName, content)); err != nil {
if _, err := configMaps.Create(toConfigMap(s.Name, name, fileName, content)); err != nil {
return err
}
}
@ -71,8 +71,8 @@ func toConfigMap(stackName, name, key string, content []byte) *apiv1.ConfigMap {
}
// createFileBasedSecrets creates a Kubernetes Secret for each Compose global file-based secret.
func (s *stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
for name, secret := range s.spec.Secrets {
func (s *Stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
for name, secret := range s.Spec.Secrets {
if secret.File == "" {
continue
}
@ -83,7 +83,7 @@ func (s *stack) createFileBasedSecrets(secrets corev1.SecretInterface) error {
return err
}
if _, err := secrets.Create(toSecret(s.name, name, fileName, content)); err != nil {
if _, err := secrets.Create(toSecret(s.Name, name, fileName, content)); err != nil {
return err
}
}

View File

@ -2,17 +2,10 @@ package kubernetes
import (
"fmt"
"io"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/schema"
composetypes "github.com/docker/cli/cli/compose/types"
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta1"
composev1beta2 "github.com/docker/cli/kubernetes/client/clientset/typed/compose/v1beta2"
v1beta1types "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/docker/cli/kubernetes/labels"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
@ -20,16 +13,17 @@ import (
// StackClient talks to a kubernetes compose component.
type StackClient interface {
CreateOrUpdate(s stack) error
StackConverter
CreateOrUpdate(s Stack) error
Delete(name string) error
Get(name string) (stack, error)
List(opts metav1.ListOptions) ([]stack, error)
IsColliding(servicesClient corev1.ServiceInterface, s stack) error
FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error)
Get(name string) (Stack, error)
List(opts metav1.ListOptions) ([]Stack, error)
IsColliding(servicesClient corev1.ServiceInterface, s Stack) error
}
// stackV1Beta1 implements stackClient interface and talks to compose component v1beta1.
type stackV1Beta1 struct {
stackV1Beta1Converter
stacks composev1beta1.StackInterface
}
@ -41,10 +35,10 @@ func newStackV1Beta1(config *rest.Config, namespace string) (*stackV1Beta1, erro
return &stackV1Beta1{stacks: client.Stacks(namespace)}, nil
}
func (s *stackV1Beta1) CreateOrUpdate(internalStack stack) error {
func (s *stackV1Beta1) CreateOrUpdate(internalStack Stack) error {
// If it already exists, update the stack
if stackBeta1, err := s.stacks.Get(internalStack.name, metav1.GetOptions{}); err == nil {
stackBeta1.Spec.ComposeFile = internalStack.composeFile
if stackBeta1, err := s.stacks.Get(internalStack.Name, metav1.GetOptions{}); err == nil {
stackBeta1.Spec.ComposeFile = internalStack.ComposeFile
_, err := s.stacks.Update(stackBeta1)
return err
}
@ -57,20 +51,20 @@ func (s *stackV1Beta1) Delete(name string) error {
return s.stacks.Delete(name, &metav1.DeleteOptions{})
}
func (s *stackV1Beta1) Get(name string) (stack, error) {
func (s *stackV1Beta1) Get(name string) (Stack, error) {
stackBeta1, err := s.stacks.Get(name, metav1.GetOptions{})
if err != nil {
return stack{}, err
return Stack{}, err
}
return stackFromV1beta1(stackBeta1)
}
func (s *stackV1Beta1) List(opts metav1.ListOptions) ([]stack, error) {
func (s *stackV1Beta1) List(opts metav1.ListOptions) ([]Stack, error) {
list, err := s.stacks.List(opts)
if err != nil {
return nil, err
}
stacks := make([]stack, len(list.Items))
stacks := make([]Stack, len(list.Items))
for i := range list.Items {
stack, err := stackFromV1beta1(&list.Items[i])
if err != nil {
@ -82,9 +76,9 @@ func (s *stackV1Beta1) List(opts metav1.ListOptions) ([]stack, error) {
}
// IsColliding verifies that services defined in the stack collides with already deployed services
func (s *stackV1Beta1) IsColliding(servicesClient corev1.ServiceInterface, st stack) error {
func (s *stackV1Beta1) IsColliding(servicesClient corev1.ServiceInterface, st Stack) error {
for _, srv := range st.getServices() {
if err := verify(servicesClient, st.name, srv); err != nil {
if err := verify(servicesClient, st.Name, srv); err != nil {
return err
}
}
@ -108,31 +102,9 @@ func verify(services corev1.ServiceInterface, stackName string, service string)
return nil
}
func (s *stackV1Beta1) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
cfg.Version = v1beta1types.MaxComposeVersion
st, err := fromCompose(stderr, name, cfg)
if err != nil {
return stack{}, err
}
res, err := yaml.Marshal(cfg)
if err != nil {
return stack{}, err
}
// reload the result to check that it produced a valid 3.5 compose file
resparsedConfig, err := loader.ParseYAML(res)
if err != nil {
return stack{}, err
}
if err = schema.Validate(resparsedConfig, v1beta1types.MaxComposeVersion); err != nil {
return stack{}, errors.Wrapf(err, "the compose yaml file is invalid with v%s", v1beta1types.MaxComposeVersion)
}
st.composeFile = string(res)
return st, nil
}
// stackV1Beta2 implements stackClient interface and talks to compose component v1beta2.
type stackV1Beta2 struct {
stackV1Beta2Converter
stacks composev1beta2.StackInterface
}
@ -144,10 +116,10 @@ func newStackV1Beta2(config *rest.Config, namespace string) (*stackV1Beta2, erro
return &stackV1Beta2{stacks: client.Stacks(namespace)}, nil
}
func (s *stackV1Beta2) CreateOrUpdate(internalStack stack) error {
func (s *stackV1Beta2) CreateOrUpdate(internalStack Stack) error {
// If it already exists, update the stack
if stackBeta2, err := s.stacks.Get(internalStack.name, metav1.GetOptions{}); err == nil {
stackBeta2.Spec = internalStack.spec
if stackBeta2, err := s.stacks.Get(internalStack.Name, metav1.GetOptions{}); err == nil {
stackBeta2.Spec = internalStack.Spec
_, err := s.stacks.Update(stackBeta2)
return err
}
@ -160,20 +132,20 @@ func (s *stackV1Beta2) Delete(name string) error {
return s.stacks.Delete(name, &metav1.DeleteOptions{})
}
func (s *stackV1Beta2) Get(name string) (stack, error) {
func (s *stackV1Beta2) Get(name string) (Stack, error) {
stackBeta2, err := s.stacks.Get(name, metav1.GetOptions{})
if err != nil {
return stack{}, err
return Stack{}, err
}
return stackFromV1beta2(stackBeta2), nil
}
func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]stack, error) {
func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]Stack, error) {
list, err := s.stacks.List(opts)
if err != nil {
return nil, err
}
stacks := make([]stack, len(list.Items))
stacks := make([]Stack, len(list.Items))
for i := range list.Items {
stacks[i] = stackFromV1beta2(&list.Items[i])
}
@ -181,17 +153,6 @@ func (s *stackV1Beta2) List(opts metav1.ListOptions) ([]stack, error) {
}
// IsColliding is handle server side with the compose api v1beta2, so nothing to do here
func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st stack) error {
func (s *stackV1Beta2) IsColliding(servicesClient corev1.ServiceInterface, st Stack) error {
return nil
}
func (s *stackV1Beta2) FromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
return fromCompose(stderr, name, cfg)
}
func fromCompose(stderr io.Writer, name string, cfg *composetypes.Config) (stack, error) {
return stack{
name: name,
spec: fromComposeConfig(stderr, cfg),
}, nil
}

View File

@ -25,18 +25,14 @@ func TestFromCompose(t *testing.T) {
},
})
assert.NilError(t, err)
assert.Equal(t, "foo", s.name)
assert.Equal(t, "foo", s.Name)
assert.Equal(t, string(`version: "3.5"
services:
bar:
image: bar
foo:
image: foo
networks: {}
volumes: {}
secrets: {}
configs: {}
`), s.composeFile)
`), s.ComposeFile)
}
func TestFromComposeUnsupportedVersion(t *testing.T) {

View File

@ -51,121 +51,134 @@ func TestStackPsErrors(t *testing.T) {
}
}
func TestRunPSWithEmptyName(t *testing.T) {
cmd := newPsCommand(test.NewFakeCli(&fakeClient{}), &orchestrator)
cmd.SetArgs([]string{"' '"})
cmd.SetOutput(ioutil.Discard)
func TestStackPs(t *testing.T) {
testCases := []struct {
doc string
taskListFunc func(types.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(string) (swarm.Node, []byte, error)
config configfile.ConfigFile
args []string
flags map[string]string
expectedErr string
golden string
}{
{
doc: "WithEmptyName",
args: []string{"' '"},
expectedErr: `invalid stack name: "' '"`,
},
{
doc: "WithEmptyStack",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{}, nil
},
args: []string{"foo"},
expectedErr: "nothing found in stack: foo",
},
{
doc: "WithQuietOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("id-foo"))}, nil
},
args: []string{"foo"},
flags: map[string]string{
"quiet": "true",
},
golden: "stack-ps-with-quiet-option.golden",
},
{
doc: "WithNoTruncOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
},
args: []string{"foo"},
flags: map[string]string{
"no-trunc": "true",
"format": "{{ .ID }}",
},
golden: "stack-ps-with-no-trunc-option.golden",
},
{
doc: "WithNoResolveOption",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
TaskNodeID("id-node-foo"),
)}, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
args: []string{"foo"},
flags: map[string]string{
"no-resolve": "true",
"format": "{{ .Node }}",
},
golden: "stack-ps-with-no-resolve-option.golden",
},
{
doc: "WithFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
args: []string{"foo"},
flags: map[string]string{
"format": "{{ .Name }}",
},
golden: "stack-ps-with-format.golden",
},
{
doc: "WithConfigFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
config: configfile.ConfigFile{
TasksFormat: "{{ .Name }}",
},
args: []string{"foo"},
golden: "stack-ps-with-config-format.golden",
},
{
doc: "WithoutFormat",
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
TaskID("id-foo"),
TaskServiceID("service-id-foo"),
TaskNodeID("id-node"),
WithTaskSpec(TaskImage("myimage:mytag")),
TaskDesiredState(swarm.TaskStateReady),
WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))),
)}, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
args: []string{"foo"},
golden: "stack-ps-without-format.golden",
},
}
assert.ErrorContains(t, cmd.Execute(), `invalid stack name: "' '"`)
}
func TestStackPsEmptyStack(t *testing.T) {
fakeCli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{}, nil
},
})
cmd := newPsCommand(fakeCli, &orchestrator)
cmd.SetArgs([]string{"foo"})
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute(), "nothing found in stack: foo")
assert.Check(t, is.Equal("", fakeCli.OutBuffer().String()))
}
func TestStackPsWithQuietOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("id-foo"))}, nil
},
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("quiet", "true")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-quiet-option.golden")
}
func TestStackPsWithNoTruncOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskID("xn4cypcov06f2w8gsbaf2lst3"))}, nil
},
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-trunc", "true")
cmd.Flags().Set("format", "{{ .ID }}")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-no-trunc-option.golden")
}
func TestStackPsWithNoResolveOption(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
TaskNodeID("id-node-foo"),
)}, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("no-resolve", "true")
cmd.Flags().Set("format", "{{ .Node }}")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-no-resolve-option.golden")
}
func TestStackPsWithFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
cmd.Flags().Set("format", "{{ .Name }}")
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-format.golden")
}
func TestStackPsWithConfigFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(TaskServiceID("service-id-foo"))}, nil
},
})
cli.SetConfigFile(&configfile.ConfigFile{
TasksFormat: "{{ .Name }}",
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-config-format.golden")
}
func TestStackPsWithoutFormat(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{*Task(
TaskID("id-foo"),
TaskServiceID("service-id-foo"),
TaskNodeID("id-node"),
WithTaskSpec(TaskImage("myimage:mytag")),
TaskDesiredState(swarm.TaskStateReady),
WithStatus(TaskState(swarm.TaskStateFailed), Timestamp(time.Now().Add(-2*time.Hour))),
)}, nil
},
nodeInspectWithRaw: func(ref string) (swarm.Node, []byte, error) {
return *Node(NodeName("node-name-bar")), nil, nil
},
})
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs([]string{"foo"})
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "stack-ps-without-format.golden")
for _, tc := range testCases {
t.Run(tc.doc, func(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
taskListFunc: tc.taskListFunc,
nodeInspectWithRaw: tc.nodeInspectWithRaw,
})
cli.SetConfigFile(&tc.config)
cmd := newPsCommand(cli, &orchestrator)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
}
cmd.SetOutput(ioutil.Discard)
if tc.expectedErr != "" {
assert.Error(t, cmd.Execute(), tc.expectedErr)
assert.Check(t, is.Equal("", cli.OutBuffer().String()))
return
}
assert.NilError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), tc.golden)
})
}
}

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