Compare commits

...
129 Commits
Author SHA1 Message Date
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Tibor Vass 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
Ian Campbell 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
Akihiro Suda 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
Andrew Hsu 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
Sebastiaan van Stijn 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
Andrew Hsu 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
Sebastiaan van Stijn 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
Brian Goff 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
Brian Goff 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
Harald Albers 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
Harald Albers 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
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Andrew Hsu 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
Andrew Hsu 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
Sebastiaan van Stijn 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
Andrew Hsu 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
Harald Albers 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
Harald Albers 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
Harald Albers 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
Sebastiaan van Stijn 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
Andrew Hsu 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
Andrew Hsu 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
Tibor Vass 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
Sebastiaan van Stijn 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
Tonis Tiigi 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
Brandon Mitchell 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
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Silvin Lubecki 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
Harald Albers 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
Silvin Lubecki 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
Akihiro Suda 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
Brandon Mitchell 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
Brandon Mitchell 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
Scott Brenner 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
Jake Lambert 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
Thomas Riccardi 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
Alex Mayer 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
Lihua Tang 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
Rui Cao 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
Anda Xu 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
Vincent Demeester 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
Akihiro Suda 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
Andrew Hsu 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
Tibor Vass 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
Andrew Hsu e245b72381 Merge pull request #1458 from dhiltgen/bump_licensing_lib
[18.09] Bump licensing lib
2018-10-19 13:58:37 -07:00
Daniel Hiltgen 0ff9e5cd10 Remove e-mail from trial flow
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-10-18 14:56:59 -07:00
Daniel Hiltgen 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
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Sebastiaan van Stijn 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
Lifubang 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
Lifubang 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
Brian Goff 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
Tibor Vass 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
Sebastiaan van Stijn 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
Tibor Vass 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
Tibor Vass 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
Akihiro Suda 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
Akihiro Suda 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
Sebastiaan van Stijn 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
Li Yi 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
Daniel Hiltgen 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
Sebastiaan van Stijn 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
Andrew Hsu 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
Daniel Hiltgen 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
Daniel Hiltgen 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
Silvin Lubecki 3e1a0bdc23 Merge pull request #1406 from dhiltgen/revamp_18.09
Remove unused helath check func
2018-10-02 11:26:36 +02:00
Daniel Hiltgen 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
Akihiro Suda 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
Andrew Hsu 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
Daniel Hiltgen 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
Andrew Hsu aca3f2d382 Merge pull request #1387 from dhiltgen/activate_ux
[18.09] Expose licensing details before loading
2018-09-28 14:02:48 -07:00
Andrew Hsu a7488d1bcd use gotest.tools/fs for TestActivateExpiredLicenseDryRun
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-09-28 20:50:43 +00:00
Daniel Hiltgen 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
Daniel Hiltgen 41910b6d68 Vendor bump of licensing lib
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-28 20:50:43 +00:00
Andrew Hsu 1a087e87c9 Merge pull request #1389 from mason-fish/fix-subscription-filter
[18.09] fix subscription filter
2018-09-26 16:12:41 -07:00
Sebastiaan van Stijn 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
Silvin Lubecki 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
Sebastiaan van Stijn 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
Mason Fish a31b20d7db fix subscription filter
Signed-off-by: Mason Fish <mason.fish@docker.com>
2018-09-24 16:56:05 -07:00
Andrew Hsu 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
Tibor Vass 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
Andrew Hsu 19e1ab273e Merge pull request #1381 from dhiltgen/revamp_18.09
[18.09] `docker engine` updates
2018-09-21 15:12:54 -07:00
Daniel Hiltgen ec1812188f Fix vendoring glitch
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-21 13:16:34 -07:00
Daniel Hiltgen 6004d74b1f Fix lint glitches
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-09-21 11:01:17 -07:00
Sebastiaan van Stijn 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
Daniel Hiltgen 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
Daniel Hiltgen 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
Christy Norman 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
Daniel Hiltgen 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
Michael Crosby cfec8027ed Install binaries on host for upgrade
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2018-09-19 19:06:28 -07:00
Silvin Lubecki 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
John Howard 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
Silvin Lubecki 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
Sebastiaan van Stijn 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
Anda Xu 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
Vincent Demeester 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
Silvin Lubecki 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
Daniel Hiltgen 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
Vincent Demeester 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
Silvin Lubecki 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
Vincent Demeester 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
Vincent Demeester 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
Akihiro Suda 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
Andrew Hsu 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
Andrew Hsu 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
Tibor Vass 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
Tibor Vass 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
Tibor Vass 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
Vincent Demeester 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
Vincent Demeester 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
Sebastiaan van Stijn 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
Marcus Martins 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
Andrew Hsu 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
Andrew Hsu 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
Daniel Hiltgen 68be7cb376 Expose product license in info output
Signed-off-by: Daniel Hiltgen <daniel.hiltgen@docker.com>
2018-08-28 20:10:09 -07:00
Andrew Hsu de805da04c Merge pull request #1317 from dhiltgen/play_nice_with_proxy
Update proxy config if present
2018-08-28 15:01:56 -07:00
Andrew Hsu 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
Daniel Hiltgen 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
Daniel Hiltgen 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
Daniel Hiltgen 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
Daniel Hiltgen 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
Sebastiaan van Stijn 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
Anda Xu 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
354 changed files with 29886 additions and 11841 deletions
+2 -2
View File
@@ -12,14 +12,14 @@ clean: ## remove build artifacts
.PHONY: test-unit .PHONY: test-unit
test-unit: ## run unit test test-unit: ## run unit test
./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/') ./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/')
.PHONY: test .PHONY: test
test: test-unit ## run tests test: test-unit ## run tests
.PHONY: test-coverage .PHONY: test-coverage
test-coverage: ## run test coverage test-coverage: ## run test coverage
./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/') ./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/')
.PHONY: lint .PHONY: lint
lint: ## run all the lint tools lint: ## run all the lint tools
+1 -1
View File
@@ -4,7 +4,7 @@ clone_folder: c:\gopath\src\github.com\docker\cli
environment: environment:
GOPATH: c:\gopath GOPATH: c:\gopath
GOVERSION: 1.10.3 GOVERSION: 1.10.8
DEPVERSION: v0.4.1 DEPVERSION: v0.4.1
install: install:
+67 -2
View File
@@ -3,29 +3,94 @@ package builder
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/spf13/cobra" "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 // NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command { func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "prune", Use: "prune",
Short: "Remove build cache", Short: "Remove build cache",
Args: cli.NoArgs, Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
report, err := dockerCli.Client().BuildCachePrune(context.Background()) spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil { if err != nil {
return err return err
} }
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(report.SpaceReclaimed))) if output != "" {
fmt.Fprintln(dockerCli.Out(), output)
}
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
return nil return nil
}, },
Annotations: map[string]string{"version": "1.39"}, 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 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})
}
+35 -22
View File
@@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"time" "time"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@@ -19,8 +20,8 @@ import (
manifeststore "github.com/docker/cli/cli/manifest/store" manifeststore "github.com/docker/cli/cli/manifest/store"
registryclient "github.com/docker/cli/cli/registry/client" registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/containerizedengine"
dopts "github.com/docker/cli/opts" dopts "github.com/docker/cli/opts"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api" "github.com/docker/docker/api"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
@@ -55,20 +56,21 @@ type Cli interface {
ManifestStore() manifeststore.Store ManifestStore() manifeststore.Store
RegistryClient(bool) registryclient.RegistryClient RegistryClient(bool) registryclient.RegistryClient
ContentTrustEnabled() bool ContentTrustEnabled() bool
NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error) NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error)
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.
// Instances of the client can be returned from NewDockerCli. // Instances of the client can be returned from NewDockerCli.
type DockerCli struct { type DockerCli struct {
configFile *configfile.ConfigFile configFile *configfile.ConfigFile
in *InStream in *InStream
out *OutStream out *OutStream
err io.Writer err io.Writer
client client.APIClient client client.APIClient
serverInfo ServerInfo serverInfo ServerInfo
clientInfo ClientInfo clientInfo ClientInfo
contentTrust bool contentTrust bool
newContainerizeClient func(string) (clitypes.ContainerizedClient, error)
} }
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified. // DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
@@ -132,6 +134,20 @@ func (cli *DockerCli) ContentTrustEnabled() bool {
return cli.contentTrust 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 // ManifestStore returns a store for local manifests
func (cli *DockerCli) ManifestStore() manifeststore.Store { func (cli *DockerCli) ManifestStore() manifeststore.Store {
// TODO: support override default location from config file // TODO: support override default location from config file
@@ -233,8 +249,8 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions
} }
// NewContainerizedEngineClient returns a containerized engine client // NewContainerizedEngineClient returns a containerized engine client
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error) { func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error) {
return containerizedengine.NewClient(sockPath) return cli.newContainerizeClient(sockPath)
} }
// ServerInfo stores details about the supported features and platform of the // ServerInfo stores details about the supported features and platform of the
@@ -252,27 +268,23 @@ type ClientInfo struct {
} }
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // 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 { 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} return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted, newContainerizeClient: containerizedFn}
} }
// NewAPIClientFromFlags creates a new APIClient from command line flags // NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
unparsedHost, err := getUnparsedServerHost(opts.Hosts) host, err := getServerHost(opts.Hosts, opts.TLSOptions)
if err != nil { if err != nil {
return &client.Client{}, err return &client.Client{}, err
} }
var clientOpts []func(*client.Client) error var clientOpts []func(*client.Client) error
helper, err := connhelper.GetConnectionHelper(unparsedHost) helper, err := connhelper.GetConnectionHelper(host)
if err != nil { if err != nil {
return &client.Client{}, err return &client.Client{}, err
} }
if helper == nil { if helper == nil {
clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions)) clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions))
host, err := dopts.ParseHost(opts.TLSOptions != nil, unparsedHost)
if err != nil {
return &client.Client{}, err
}
clientOpts = append(clientOpts, client.WithHost(host)) clientOpts = append(clientOpts, client.WithHost(host))
} else { } else {
clientOpts = append(clientOpts, func(c *client.Client) error { clientOpts = append(clientOpts, func(c *client.Client) error {
@@ -305,7 +317,7 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
return client.NewClientWithOpts(clientOpts...) return client.NewClientWithOpts(clientOpts...)
} }
func getUnparsedServerHost(hosts []string) (string, error) { func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
var host string var host string
switch len(hosts) { switch len(hosts) {
case 0: case 0:
@@ -315,7 +327,8 @@ func getUnparsedServerHost(hosts []string) (string, error) {
default: default:
return "", errors.New("Please specify only one -H") return "", errors.New("Please specify only one -H")
} }
return host, nil
return dopts.ParseHost(tlsOptions != nil, host)
} }
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error { func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
+20
View File
@@ -43,6 +43,26 @@ func TestNewAPIClientFromFlags(t *testing.T) {
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion())) 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) { func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
customVersion := "v3.3.3" customVersion := "v3.3.3"
defer env.Patch(t, "DOCKER_API_VERSION", customVersion)() defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()
+5 -4
View File
@@ -2,6 +2,7 @@ package commands
import ( import (
"os" "os"
"runtime"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/builder" "github.com/docker/cli/cli/command/builder"
@@ -85,9 +86,6 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
// volume // volume
volume.NewVolumeCommand(dockerCli), volume.NewVolumeCommand(dockerCli),
// engine
engine.NewEngineCommand(dockerCli),
// legacy commands may be hidden // legacy commands may be hidden
hide(system.NewEventsCommand(dockerCli)), hide(system.NewEventsCommand(dockerCli)),
hide(system.NewInfoCommand(dockerCli)), hide(system.NewInfoCommand(dockerCli)),
@@ -124,7 +122,10 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
hide(image.NewSaveCommand(dockerCli)), hide(image.NewSaveCommand(dockerCli)),
hide(image.NewTagCommand(dockerCli)), hide(image.NewTagCommand(dockerCli)),
) )
if runtime.GOOS == "linux" {
// engine
cmd.AddCommand(engine.NewEngineCommand(dockerCli))
}
} }
func hide(cmd *cobra.Command) *cobra.Command { func hide(cmd *cobra.Command) *cobra.Command {
+1 -1
View File
@@ -73,6 +73,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
// RunPrune calls the Container Prune API // RunPrune calls the Container Prune API
// This returns the amount of space reclaimed and a detailed output string // 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}) return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
} }
+49 -21
View File
@@ -3,11 +3,12 @@ package engine
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/containerizedengine"
"github.com/docker/cli/internal/licenseutils" "github.com/docker/cli/internal/licenseutils"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/licensing/model" "github.com/docker/licensing/model"
"github.com/pkg/errors" "github.com/pkg/errors"
@@ -15,19 +16,21 @@ import (
) )
type activateOptions struct { type activateOptions struct {
licenseFile string licenseFile string
version string version string
registryPrefix string registryPrefix string
format string format string
image string image string
quiet bool quiet bool
displayOnly bool displayOnly bool
sockPath string sockPath string
licenseLoginFunc func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error)
} }
// newActivateCommand creates a new `docker engine activate` command // newActivateCommand creates a new `docker engine activate` command
func newActivateCommand(dockerCli command.Cli) *cobra.Command { func newActivateCommand(dockerCli command.Cli) *cobra.Command {
var options activateOptions var options activateOptions
options.licenseLoginFunc = licenseutils.Login
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "activate [OPTIONS]", Use: "activate [OPTIONS]",
@@ -56,10 +59,10 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
flags.StringVar(&options.licenseFile, "license", "", "License File") 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.version, "version", "", "Specify engine version (default is to use currently running version)")
flags.StringVar(&options.registryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled") flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled")
flags.StringVar(&options.image, "engine-image", containerizedengine.EnterpriseEngineImage, "Specify engine image") flags.StringVar(&options.image, "engine-image", "", "Specify engine image")
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template") flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit") 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.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint") flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
@@ -67,6 +70,9 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
} }
func runActivate(cli command.Cli, options activateOptions) error { 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() ctx := context.Background()
client, err := cli.NewContainerizedEngineClient(options.sockPath) client, err := cli.NewContainerizedEngineClient(options.sockPath)
if err != nil { if err != nil {
@@ -94,26 +100,48 @@ func runActivate(cli command.Cli, options activateOptions) error {
return err return err
} }
} }
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil { 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 return err
} }
opts := containerizedengine.EngineInitOptions{ // 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, RegistryPrefix: options.registryPrefix,
EngineImage: options.image, EngineImage: options.image,
EngineVersion: options.version, EngineVersion: options.version,
} }
return client.ActivateEngine(ctx, opts, cli.Out(), authConfig, if err := client.ActivateEngine(ctx, opts, cli.Out(), authConfig); err != nil {
func(ctx context.Context) error { return err
client := cli.Client() }
_, err := client.Ping(ctx) fmt.Fprintln(cli.Out(), `Successfully activated engine.
return err 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) { func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
user, err := licenseutils.Login(ctx, authConfig) user, err := options.licenseLoginFunc(ctx, authConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+112 -3
View File
@@ -1,19 +1,35 @@
package engine package engine
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/docker/cli/internal/containerizedengine" "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/assert"
"gotest.tools/fs"
"gotest.tools/golden"
)
const (
// nolint: lll
expiredLicense = `{"key_id":"irlYm3b9fdD8hMUXjazF39im7VQSSbAm9tfHK8cKUxJt","private_key":"aH5tTRDAVJpCRS2CRetTQVXIKgWUPfoCHODhDvNPvAbz","authorization":"ewogICAicGF5bG9hZCI6ICJleUpsZUhCcGNtRjBhVzl1SWpvaU1qQXhPQzB3TXkweE9GUXdOem93TURvd01Gb2lMQ0owYjJ0bGJpSTZJbkZtTVMxMlVtRmtialp5YjFaMldXdHJlVXN4VFdKMGNGUmpXR1ozVjA4MVRWZFFTM2cwUnpJd2NIYzlJaXdpYldGNFJXNW5hVzVsY3lJNk1Td2ljMk5oYm01cGJtZEZibUZpYkdWa0lqcDBjblZsTENKc2FXTmxibk5sVkhsd1pTSTZJazltWm14cGJtVWlMQ0owYVdWeUlqb2lVSEp2WkhWamRHbHZiaUo5IiwKICAgInNpZ25hdHVyZXMiOiBbCiAgICAgIHsKICAgICAgICAgImhlYWRlciI6IHsKICAgICAgICAgICAgImp3ayI6IHsKICAgICAgICAgICAgICAgImUiOiAiQVFBQiIsCiAgICAgICAgICAgICAgICJrZXlJRCI6ICJKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVCIsCiAgICAgICAgICAgICAgICJraWQiOiAiSjdMRDo2N1ZSOkw1SFo6VTdCQToyTzRHOjRBTDM6T0YyTjpKSEdCOkVGVEg6NUNWUTpNRkVPOkFFSVQiLAogICAgICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICAgICJuIjogInlkSXktbFU3bzdQY2VZLTQtcy1DUTVPRWdDeUY4Q3hJY1FJV3VLODRwSWlaY2lZNjczMHlDWW53TFNLVGx3LVU2VUNfUVJlV1Jpb01OTkU1RHM1VFlFWGJHRzZvbG0ycWRXYkJ3Y0NnLTJVVUhfT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTFJdi1tcTdscDVuUV9SeHJUMEtaUkFRVFlMRU1FZkd3bTNoTU9fZ2VMUFMtaGdLUHRJSGxrZzZfV2NveFRHb0tQNzlkX3dhSFl4R05sN1doU25laUJTeGJwYlFBS2syMWxnNzk4WGI3dlp5RUFURE1yUlI5TWVFNkFkajVISnBZM0NveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTy13YTJLTjhVd2dJSW0waTVJMXVXOUdrcTR6akJ5NXpoZ3F1VVhiRzliV1BBT1lycTVRYTgxRHhHY0JsSnlIWUFwLUREUEU5VEdnNHpZbVhqSm54WnFIRWR1R3FkZXZaOFhNSTB1a2ZrR0lJMTR3VU9pTUlJSXJYbEVjQmZfNDZJOGdRV0R6eHljWmVfSkdYLUxBdWF5WHJ5clVGZWhWTlVkWlVsOXdYTmFKQi1rYUNxejVRd2FSOTNzR3ctUVNmdEQwTnZMZTdDeU9ILUU2dmc2U3RfTmVUdmd2OFluaENpWElsWjhIT2ZJd05lN3RFRl9VY3o1T2JQeWttM3R5bHJOVWp0MFZ5QW10dGFjVkkyaUdpaGNVUHJtazRsVklaN1ZEX0xTVy1pN3lvU3VydHBzUFhjZTJwS0RJbzMwbEpHaE9fM0tVbWwyU1VaQ3F6SjF5RW1LcHlzSDVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiYWxnIjogIlJTMjU2IgogICAgICAgICB9LAogICAgICAgICAic2lnbmF0dXJlIjogIm5saTZIdzRrbW5KcTBSUmRXaGVfbkhZS2VJLVpKenM1U0d5SUpDakh1dWtnVzhBYklpVzFZYWJJR2NqWUt0QTY4dWN6T1hyUXZreGxWQXJLSlgzMDJzN0RpbzcxTlNPRzJVcnhsSjlibDFpd0F3a3ZyTEQ2T0p5MGxGLVg4WnRabXhPVmNQZmwzcmJwZFQ0dnlnWTdNcU1QRXdmb0IxTmlWZDYyZ1cxU2NSREZZcWw3R0FVaFVKNkp4QU15VzVaOXl5YVE0NV8wd0RMUk5mRjA5YWNXeVowTjRxVS1hZjhrUTZUUWZUX05ERzNCR3pRb2V3cHlEajRiMFBHb0diOFhLdDlwekpFdEdxM3lQM25VMFFBbk90a2gwTnZac1l1UFcyUnhDT3lRNEYzVlR3UkF2eF9HSTZrMVRpYmlKNnByUWluUy16Sjh6RE8zUjBuakE3OFBwNXcxcVpaUE9BdmtzZFNSYzJDcVMtcWhpTmF5YUhOVHpVNnpyOXlOZHR2S0o1QjNST0FmNUtjYXNiWURjTnVpeXBUNk90LUtqQ2I1dmYtWVpnc2FRNzJBdFBhSU4yeUpNREZHbmEwM0hpSjMxcTJRUlp5eTZrd3RYaGtwcDhTdEdIcHYxSWRaV09SVWttb0g5SFBzSGk4SExRLTZlM0tEY2x1RUQyMTNpZnljaVhtN0YzdHdaTTNHeDd1UXR1SldHaUlTZ2Z0QW9lVjZfUmI2VThkMmZxNzZuWHYxak5nckRRcE5waEZFd2tCdGRtZHZ2THByZVVYX3BWangza1AxN3pWbXFKNmNOOWkwWUc4WHg2VmRzcUxsRXUxQ2Rhd3Q0eko1M3VHMFlKTjRnUDZwc25yUS1uM0U1aFdlMDJ3d3dBZ3F3bGlPdmd4V1RTeXJyLXY2eDI0IiwKICAgICAgICAgInByb3RlY3RlZCI6ICJleUptYjNKdFlYUk1aVzVuZEdnaU9qRTNNeXdpWm05eWJXRjBWR0ZwYkNJNkltWlJJaXdpZEdsdFpTSTZJakl3TVRjdE1EVXRNRFZVTWpFNk5UYzZNek5hSW4wIgogICAgICB9CiAgIF0KfQ=="}`
) )
func TestActivateNoContainerd(t *testing.T) { func TestActivateNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient( testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) { func(string) (clitypes.ContainerizedClient, error) {
return nil, fmt.Errorf("some error") return nil, fmt.Errorf("some error")
}, },
) )
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli) cmd := newActivateCommand(testCli)
cmd.Flags().Set("license", "invalidpath") cmd.Flags().Set("license", "invalidpath")
cmd.SilenceUsage = true cmd.SilenceUsage = true
@@ -24,10 +40,11 @@ func TestActivateNoContainerd(t *testing.T) {
func TestActivateBadLicense(t *testing.T) { func TestActivateBadLicense(t *testing.T) {
testCli.SetContainerizedEngineClient( testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) { func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil return &fakeContainerizedEngineClient{}, nil
}, },
) )
isRoot = func() bool { return true }
cmd := newActivateCommand(testCli) cmd := newActivateCommand(testCli)
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true
@@ -35,3 +52,95 @@ func TestActivateBadLicense(t *testing.T) {
err := cmd.Execute() err := cmd.Execute()
assert.Error(t, err, "open invalidpath: no such file or directory") 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")
}
+13
View File
@@ -0,0 +1,13 @@
// +build !windows
package engine
import (
"golang.org/x/sys/unix"
)
var (
isRoot = func() bool {
return unix.Geteuid() == 0
}
)
+9
View File
@@ -0,0 +1,9 @@
// +build windows
package engine
var (
isRoot = func() bool {
return true
}
)
+2 -1
View File
@@ -5,6 +5,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry" registrytypes "github.com/docker/docker/api/types/registry"
@@ -13,7 +14,7 @@ import (
func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) { func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
if registryPrefix == "" { if registryPrefix == "" {
registryPrefix = "docker.io/docker" registryPrefix = clitypes.RegistryPrefix
} }
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix) distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
if err != nil { if err != nil {
+25 -33
View File
@@ -7,18 +7,16 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/internal/containerizedengine" "github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
const (
releaseNotePrefix = "https://docs.docker.com/releasenotes"
)
type checkOptions struct { type checkOptions struct {
registryPrefix string registryPrefix string
preReleases bool preReleases bool
engineImage string
downgrades bool downgrades bool
upgrades bool upgrades bool
format string format string
@@ -38,9 +36,10 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVar(&options.registryPrefix, "registry-prefix", "", "Override the existing location where engine images are pulled") 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.downgrades, "downgrades", false, "Report downgrades (default omits older versions)")
flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release 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.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template") flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions") flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions")
@@ -50,54 +49,47 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
} }
func runCheck(dockerCli command.Cli, options checkOptions) error { 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() ctx := context.Background()
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath) client := dockerCli.Client()
if err != nil { serverVersion, err := client.ServerVersion(ctx)
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
currentOpts, err := client.GetCurrentEngineVersion(ctx)
if err != nil { if err != nil {
return err return err
} }
// override with user provided prefix if specified availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, options.engineImage, serverVersion.Version)
if options.registryPrefix != "" {
currentOpts.RegistryPrefix = options.registryPrefix
}
imageName := currentOpts.RegistryPrefix + "/" + currentOpts.EngineImage
currentVersion := currentOpts.EngineVersion
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
if err != nil { if err != nil {
return err return err
} }
availUpdates := []containerizedengine.Update{ availUpdates := []clitypes.Update{
{Type: "current", Version: currentVersion}, {Type: "current", Version: serverVersion.Version},
} }
if len(versions.Patches) > 0 { if len(availVersions.Patches) > 0 {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"patch", "patch",
options.preReleases, options.preReleases,
versions.Patches)...) availVersions.Patches)...)
} }
if options.upgrades { if options.upgrades {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"upgrade", "upgrade",
options.preReleases, options.preReleases,
versions.Upgrades)...) availVersions.Upgrades)...)
} }
if options.downgrades { if options.downgrades {
availUpdates = append(availUpdates, availUpdates = append(availUpdates,
processVersions( processVersions(
currentVersion, serverVersion.Version,
"downgrade", "downgrade",
options.preReleases, options.preReleases,
versions.Downgrades)...) availVersions.Downgrades)...)
} }
format := options.format format := options.format
@@ -115,17 +107,17 @@ func runCheck(dockerCli command.Cli, options checkOptions) error {
func processVersions(currentVersion, verType string, func processVersions(currentVersion, verType string,
includePrerelease bool, includePrerelease bool,
versions []containerizedengine.DockerVersion) []containerizedengine.Update { availVersions []clitypes.DockerVersion) []clitypes.Update {
availUpdates := []containerizedengine.Update{} availUpdates := []clitypes.Update{}
for _, ver := range versions { for _, ver := range availVersions {
if !includePrerelease && ver.Prerelease() != "" { if !includePrerelease && ver.Prerelease() != "" {
continue continue
} }
if ver.Tag != currentVersion { if ver.Tag != currentVersion {
availUpdates = append(availUpdates, containerizedengine.Update{ availUpdates = append(availUpdates, clitypes.Update{
Type: verType, Type: verType,
Version: ver.Tag, Version: ver.Tag,
Notes: fmt.Sprintf("%s/%s", releaseNotePrefix, ver.Tag), Notes: fmt.Sprintf("%s?%s", clitypes.ReleaseNotePrefix, ver.Tag),
}) })
} }
} }
+67 -96
View File
@@ -5,11 +5,13 @@ import (
"fmt" "fmt"
"testing" "testing"
registryclient "github.com/docker/cli/cli/registry/client" manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/containerizedengine"
"github.com/docker/cli/internal/test" "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/docker/docker/client"
ver "github.com/hashicorp/go-version" "github.com/opencontainers/go-digest"
"gotest.tools/assert" "gotest.tools/assert"
"gotest.tools/golden" "gotest.tools/golden"
) )
@@ -18,126 +20,95 @@ var (
testCli = test.NewFakeCli(&client.Client{}) testCli = test.NewFakeCli(&client.Client{})
) )
func TestCheckForUpdatesNoContainerd(t *testing.T) { type verClient struct {
testCli.SetContainerizedEngineClient( client.Client
func(string) (containerizedengine.Client, error) { ver types.Version
return nil, fmt.Errorf("some error") verErr error
}, info types.Info
) infoErr error
cmd := newCheckForUpdatesCommand(testCli) }
cmd.SilenceUsage = true
cmd.SilenceErrors = true func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) {
err := cmd.Execute() return c.ver, c.verErr
assert.ErrorContains(t, err, "unable to access local containerd") }
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) { func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
retErr := fmt.Errorf("some failure") isRoot = func() bool { return true }
getCurrentEngineVersionFunc := func(ctx context.Context) (containerizedengine.EngineInitOptions, error) { c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
return containerizedengine.EngineInitOptions{}, retErr c.SetRegistryClient(testRegistryClient{})
} cmd := newCheckForUpdatesCommand(c)
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.SilenceUsage = true cmd.SilenceUsage = true
cmd.SilenceErrors = true cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()
assert.Assert(t, err == retErr) assert.ErrorContains(t, err, "no such file or directory")
}
func TestCheckForUpdatesGetEngineVersionsFail(t *testing.T) {
retErr := fmt.Errorf("some failure")
getEngineVersionsFunc := func(ctx context.Context,
registryClient registryclient.RegistryClient,
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) {
return containerizedengine.AvailableVersions{}, retErr
}
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{
getEngineVersionsFunc: getEngineVersionsFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.Assert(t, err == retErr)
} }
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) { func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
getCurrentEngineVersionFunc := func(ctx context.Context) (containerizedengine.EngineInitOptions, error) { c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
return containerizedengine.EngineInitOptions{ c.SetRegistryClient(testRegistryClient{[]string{
EngineImage: "current engine", "1.0.1", "1.0.2", "1.0.3-beta1",
EngineVersion: "1.1.0", "1.1.1", "1.1.2", "1.1.3-beta1",
}, nil "1.2.0", "2.0.0", "2.1.0-beta1",
} }})
getEngineVersionsFunc := func(ctx context.Context,
registryClient registryclient.RegistryClient, isRoot = func() bool { return true }
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) { cmd := newCheckForUpdatesCommand(c)
return containerizedengine.AvailableVersions{
Downgrades: parseVersions(t, "1.0.1", "1.0.2", "1.0.3-beta1"),
Patches: parseVersions(t, "1.1.1", "1.1.2", "1.1.3-beta1"),
Upgrades: parseVersions(t, "1.2.0", "2.0.0", "2.1.0-beta1"),
}, nil
}
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{
getEngineVersionsFunc: getEngineVersionsFunc,
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
}, nil
},
)
cmd := newCheckForUpdatesCommand(testCli)
cmd.Flags().Set("pre-releases", "true") cmd.Flags().Set("pre-releases", "true")
cmd.Flags().Set("downgrades", "true") cmd.Flags().Set("downgrades", "true")
cmd.Flags().Set("engine-image", "engine-community")
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute() err := cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
golden.Assert(t, testCli.OutBuffer().String(), "check-all.golden") golden.Assert(t, c.OutBuffer().String(), "check-all.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "true") cmd.Flags().Set("downgrades", "true")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-no-prerelease.golden") golden.Assert(t, c.OutBuffer().String(), "check-no-prerelease.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false") cmd.Flags().Set("downgrades", "false")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-no-downgrades.golden") golden.Assert(t, c.OutBuffer().String(), "check-no-downgrades.golden")
testCli.OutBuffer().Reset() c.OutBuffer().Reset()
cmd.Flags().Set("pre-releases", "false") cmd.Flags().Set("pre-releases", "false")
cmd.Flags().Set("downgrades", "false") cmd.Flags().Set("downgrades", "false")
cmd.Flags().Set("upgrades", "false") cmd.Flags().Set("upgrades", "false")
err = cmd.Execute() err = cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
fmt.Println(testCli.OutBuffer().String()) fmt.Println(c.OutBuffer().String())
golden.Assert(t, testCli.OutBuffer().String(), "check-patches-only.golden") golden.Assert(t, c.OutBuffer().String(), "check-patches-only.golden")
}
func makeVersion(t *testing.T, tag string) containerizedengine.DockerVersion {
v, err := ver.NewVersion(tag)
assert.NilError(t, err)
return containerizedengine.DockerVersion{Version: *v, Tag: tag}
}
func parseVersions(t *testing.T, tags ...string) []containerizedengine.DockerVersion {
ret := make([]containerizedengine.DockerVersion, len(tags))
for i, tag := range tags {
ret[i] = makeVersion(t, tag)
}
return ret
} }
+28 -32
View File
@@ -5,7 +5,7 @@ import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
registryclient "github.com/docker/cli/cli/registry/client" registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/cli/internal/containerizedengine" clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
) )
@@ -13,28 +13,26 @@ type (
fakeContainerizedEngineClient struct { fakeContainerizedEngineClient struct {
closeFunc func() error closeFunc func() error
activateEngineFunc func(ctx context.Context, activateEngineFunc func(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig) error
healthfn func(context.Context) error) error
initEngineFunc func(ctx context.Context, initEngineFunc func(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig,
healthfn func(context.Context) error) error healthfn func(context.Context) error) error
doUpdateFunc func(ctx context.Context, doUpdateFunc func(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig) error
healthfn func(context.Context) error) error
getEngineVersionsFunc func(ctx context.Context, getEngineVersionsFunc func(ctx context.Context,
registryClient registryclient.RegistryClient, registryClient registryclient.RegistryClient,
currentVersion, currentVersion,
imageName string) (containerizedengine.AvailableVersions, error) imageName string) (clitypes.AvailableVersions, error)
getEngineFunc func(ctx context.Context) (containerd.Container, error) getEngineFunc func(ctx context.Context) (containerd.Container, error)
removeEngineFunc func(ctx context.Context, engine containerd.Container) error removeEngineFunc func(ctx context.Context) error
getCurrentEngineVersionFunc func(ctx context.Context) (containerizedengine.EngineInitOptions, error) getCurrentEngineVersionFunc func(ctx context.Context) (clitypes.EngineInitOptions, error)
} }
) )
@@ -46,18 +44,17 @@ func (w *fakeContainerizedEngineClient) Close() error {
} }
func (w *fakeContainerizedEngineClient) ActivateEngine(ctx context.Context, func (w *fakeContainerizedEngineClient) ActivateEngine(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig) error {
healthfn func(context.Context) error) error {
if w.activateEngineFunc != nil { if w.activateEngineFunc != nil {
return w.activateEngineFunc(ctx, opts, out, authConfig, healthfn) return w.activateEngineFunc(ctx, opts, out, authConfig)
} }
return nil return nil
} }
func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context, func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig,
healthfn func(context.Context) error) error { healthfn func(context.Context) error) error {
if w.initEngineFunc != nil { if w.initEngineFunc != nil {
@@ -66,23 +63,22 @@ func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
return nil return nil
} }
func (w *fakeContainerizedEngineClient) DoUpdate(ctx context.Context, func (w *fakeContainerizedEngineClient) DoUpdate(ctx context.Context,
opts containerizedengine.EngineInitOptions, opts clitypes.EngineInitOptions,
out containerizedengine.OutStream, out clitypes.OutStream,
authConfig *types.AuthConfig, authConfig *types.AuthConfig) error {
healthfn func(context.Context) error) error {
if w.doUpdateFunc != nil { if w.doUpdateFunc != nil {
return w.doUpdateFunc(ctx, opts, out, authConfig, healthfn) return w.doUpdateFunc(ctx, opts, out, authConfig)
} }
return nil return nil
} }
func (w *fakeContainerizedEngineClient) GetEngineVersions(ctx context.Context, func (w *fakeContainerizedEngineClient) GetEngineVersions(ctx context.Context,
registryClient registryclient.RegistryClient, registryClient registryclient.RegistryClient,
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) { currentVersion, imageName string) (clitypes.AvailableVersions, error) {
if w.getEngineVersionsFunc != nil { if w.getEngineVersionsFunc != nil {
return w.getEngineVersionsFunc(ctx, registryClient, currentVersion, imageName) return w.getEngineVersionsFunc(ctx, registryClient, currentVersion, imageName)
} }
return containerizedengine.AvailableVersions{}, nil return clitypes.AvailableVersions{}, nil
} }
func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containerd.Container, error) { func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containerd.Container, error) {
@@ -91,15 +87,15 @@ func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containe
} }
return nil, nil return nil, nil
} }
func (w *fakeContainerizedEngineClient) RemoveEngine(ctx context.Context, engine containerd.Container) error { func (w *fakeContainerizedEngineClient) RemoveEngine(ctx context.Context) error {
if w.removeEngineFunc != nil { if w.removeEngineFunc != nil {
return w.removeEngineFunc(ctx, engine) return w.removeEngineFunc(ctx)
} }
return nil return nil
} }
func (w *fakeContainerizedEngineClient) GetCurrentEngineVersion(ctx context.Context) (containerizedengine.EngineInitOptions, error) { func (w *fakeContainerizedEngineClient) GetCurrentEngineVersion(ctx context.Context) (clitypes.EngineInitOptions, error) {
if w.getCurrentEngineVersionFunc != nil { if w.getCurrentEngineVersionFunc != nil {
return w.getCurrentEngineVersionFunc(ctx) return w.getCurrentEngineVersionFunc(ctx)
} }
return containerizedengine.EngineInitOptions{}, nil return clitypes.EngineInitOptions{}, nil
} }
-2
View File
@@ -15,11 +15,9 @@ func NewEngineCommand(dockerCli command.Cli) *cobra.Command {
RunE: command.ShowHelp(dockerCli.Err()), RunE: command.ShowHelp(dockerCli.Err()),
} }
cmd.AddCommand( cmd.AddCommand(
newInitCommand(dockerCli),
newActivateCommand(dockerCli), newActivateCommand(dockerCli),
newCheckForUpdatesCommand(dockerCli), newCheckForUpdatesCommand(dockerCli),
newUpdateCommand(dockerCli), newUpdateCommand(dockerCli),
newRmCommand(dockerCli),
) )
return cmd return cmd
} }
+1 -1
View File
@@ -10,5 +10,5 @@ func TestNewEngineCommand(t *testing.T) {
cmd := NewEngineCommand(testCli) cmd := NewEngineCommand(testCli)
subcommands := cmd.Commands() subcommands := cmd.Commands()
assert.Assert(t, len(subcommands) == 5) assert.Assert(t, len(subcommands) == 3)
} }
+2 -54
View File
@@ -1,62 +1,10 @@
package engine package engine
import ( import (
"context" clitypes "github.com/docker/cli/types"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/containerizedengine"
"github.com/pkg/errors"
"github.com/spf13/cobra"
) )
type extendedEngineInitOptions struct { type extendedEngineInitOptions struct {
containerizedengine.EngineInitOptions clitypes.EngineInitOptions
sockPath string sockPath string
} }
func newInitCommand(dockerCli command.Cli) *cobra.Command {
var options extendedEngineInitOptions
cmd := &cobra.Command{
Use: "init [OPTIONS]",
Short: "Initialize a local engine",
Long: `This command will initialize a local engine running on containerd.
Configuration of the engine is managed through the daemon.json configuration
file on the host and may be pre-created before running the 'init' command.
`,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runInit(dockerCli, options)
},
Annotations: map[string]string{"experimentalCLI": ""},
}
flags := cmd.Flags()
flags.StringVar(&options.EngineVersion, "version", cli.Version, "Specify engine version")
flags.StringVar(&options.EngineImage, "engine-image", containerizedengine.CommunityEngineImage, "Specify engine image")
flags.StringVar(&options.RegistryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled")
flags.StringVar(&options.ConfigFile, "config-file", "/etc/docker/daemon.json", "Specify the location of the daemon configuration file on the host")
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runInit(dockerCli command.Cli, options extendedEngineInitOptions) error {
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
}
return client.InitEngine(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
func(ctx context.Context) error {
client := dockerCli.Client()
_, err := client.Ping(ctx)
return err
})
}
-33
View File
@@ -1,33 +0,0 @@
package engine
import (
"fmt"
"testing"
"github.com/docker/cli/internal/containerizedengine"
"gotest.tools/assert"
)
func TestInitNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return nil, fmt.Errorf("some error")
},
)
cmd := newInitCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}
func TestInitHappy(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newInitCommand(testCli)
err := cmd.Execute()
assert.NilError(t, err)
}
-54
View File
@@ -1,54 +0,0 @@
package engine
import (
"context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// TODO - consider adding a "purge" flag that also removes
// configuration files and the docker root dir.
type rmOptions struct {
sockPath string
}
func newRmCommand(dockerCli command.Cli) *cobra.Command {
var options rmOptions
cmd := &cobra.Command{
Use: "rm [OPTIONS]",
Short: "Remove the local engine",
Long: `This command will remove the local engine running on containerd.
No state files will be removed from the host filesystem.
`,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runRm(dockerCli, options)
},
Annotations: map[string]string{"experimentalCLI": ""},
}
flags := cmd.Flags()
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd
}
func runRm(dockerCli command.Cli, options rmOptions) error {
ctx := context.Background()
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
if err != nil {
return errors.Wrap(err, "unable to access local containerd")
}
defer client.Close()
engine, err := client.GetEngine(ctx)
if err != nil {
return err
}
return client.RemoveEngine(ctx, engine)
}
-33
View File
@@ -1,33 +0,0 @@
package engine
import (
"fmt"
"testing"
"github.com/docker/cli/internal/containerizedengine"
"gotest.tools/assert"
)
func TestRmNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return nil, fmt.Errorf("some error")
},
)
cmd := newRmCommand(testCli)
cmd.SilenceUsage = true
cmd.SilenceErrors = true
err := cmd.Execute()
assert.ErrorContains(t, err, "unable to access local containerd")
}
func TestRmHappy(t *testing.T) {
testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) {
return &fakeContainerizedEngineClient{}, nil
},
)
cmd := newRmCommand(testCli)
err := cmd.Execute()
assert.NilError(t, err)
}
+9 -9
View File
@@ -1,11 +1,11 @@
TYPE VERSION NOTES TYPE VERSION NOTES
current 1.1.0 current 1.1.0
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1 patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2 patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
patch 1.1.3-beta1 https://docs.docker.com/releasenotes/1.1.3-beta1 patch 1.1.3-beta1 https://docker.com/engine/releasenotes?1.1.3-beta1
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0 upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0 upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
upgrade 2.1.0-beta1 https://docs.docker.com/releasenotes/2.1.0-beta1 upgrade 2.1.0-beta1 https://docker.com/engine/releasenotes?2.1.0-beta1
downgrade 1.0.1 https://docs.docker.com/releasenotes/1.0.1 downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
downgrade 1.0.2 https://docs.docker.com/releasenotes/1.0.2 downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2
downgrade 1.0.3-beta1 https://docs.docker.com/releasenotes/1.0.3-beta1 downgrade 1.0.3-beta1 https://docker.com/engine/releasenotes?1.0.3-beta1
+4 -4
View File
@@ -1,6 +1,6 @@
TYPE VERSION NOTES TYPE VERSION NOTES
current 1.1.0 current 1.1.0
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1 patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2 patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0 upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0 upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
+6 -6
View File
@@ -1,8 +1,8 @@
TYPE VERSION NOTES TYPE VERSION NOTES
current 1.1.0 current 1.1.0
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1 patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2 patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0 upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0 upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
downgrade 1.0.1 https://docs.docker.com/releasenotes/1.0.1 downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
downgrade 1.0.2 https://docs.docker.com/releasenotes/1.0.2 downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2
+2 -2
View File
@@ -1,4 +1,4 @@
TYPE VERSION NOTES TYPE VERSION NOTES
current 1.1.0 current 1.1.0
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1 patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2 patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
@@ -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
@@ -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
+9 -22
View File
@@ -6,6 +6,7 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
clitypes "github.com/docker/cli/types"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -24,45 +25,31 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version") flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version")
flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image") flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
flags.StringVar(&options.RegistryPrefix, "registry-prefix", "", "Override the current location where engine images are pulled") 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") flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
return cmd return cmd
} }
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error { 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() ctx := context.Background()
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath) client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
if err != nil { if err != nil {
return errors.Wrap(err, "unable to access local containerd") return errors.Wrap(err, "unable to access local containerd")
} }
defer client.Close() defer client.Close()
if options.EngineImage == "" || options.RegistryPrefix == "" {
currentOpts, err := client.GetCurrentEngineVersion(ctx)
if err != nil {
return err
}
if options.EngineImage == "" {
options.EngineImage = currentOpts.EngineImage
}
if options.RegistryPrefix == "" {
options.RegistryPrefix = currentOpts.RegistryPrefix
}
}
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix) authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
if err != nil { if err != nil {
return err return err
} }
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig); err != nil {
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
func(ctx context.Context) error {
client := dockerCli.Client()
_, err := client.Ping(ctx)
return err
}); err != nil {
return err return err
} }
fmt.Fprintln(dockerCli.Out(), "Success! The docker engine is now running.") fmt.Fprintln(dockerCli.Out(), `Successfully updated engine.
Restart docker with 'systemctl restart docker' to complete the update.`)
return nil return nil
} }
+11 -6
View File
@@ -4,13 +4,16 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/docker/cli/internal/containerizedengine" "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" "gotest.tools/assert"
) )
func TestUpdateNoContainerd(t *testing.T) { func TestUpdateNoContainerd(t *testing.T) {
testCli.SetContainerizedEngineClient( testCli.SetContainerizedEngineClient(
func(string) (containerizedengine.Client, error) { func(string) (clitypes.ContainerizedClient, error) {
return nil, fmt.Errorf("some error") return nil, fmt.Errorf("some error")
}, },
) )
@@ -22,14 +25,16 @@ func TestUpdateNoContainerd(t *testing.T) {
} }
func TestUpdateHappy(t *testing.T) { func TestUpdateHappy(t *testing.T) {
testCli.SetContainerizedEngineClient( c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
func(string) (containerizedengine.Client, error) { c.SetContainerizedEngineClient(
func(string) (clitypes.ContainerizedClient, error) {
return &fakeContainerizedEngineClient{}, nil return &fakeContainerizedEngineClient{}, nil
}, },
) )
cmd := newUpdateCommand(testCli) cmd := newUpdateCommand(c)
cmd.Flags().Set("registry-prefix", "docker.io/docker") cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix)
cmd.Flags().Set("version", "someversion") cmd.Flags().Set("version", "someversion")
cmd.Flags().Set("engine-image", "someimage")
err := cmd.Execute() err := cmd.Execute()
assert.NilError(t, err) assert.NilError(t, err)
} }
+179
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)
}
+102 -51
View File
@@ -12,19 +12,11 @@ import (
) )
const ( const (
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}" 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}} ago\t{{.Status}}\t{{.Names}}" defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}" defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
defaultBuildCacheVerboseFormat = ` defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
ID: {{.ID}}
Description: {{.Description}}
Mutable: {{.Mutable}}
Size: {{.Size}}
CreatedAt: {{.CreatedAt}}
LastUsedAt: {{.LastUsedAt}}
UsageCount: {{.UsageCount}}
`
typeHeader = "TYPE" typeHeader = "TYPE"
totalHeader = "TOTAL" totalHeader = "TOTAL"
@@ -32,7 +24,7 @@ UsageCount: {{.UsageCount}}
reclaimableHeader = "RECLAIMABLE" reclaimableHeader = "RECLAIMABLE"
containersHeader = "CONTAINERS" containersHeader = "CONTAINERS"
sharedSizeHeader = "SHARED SIZE" sharedSizeHeader = "SHARED SIZE"
uniqueSizeHeader = "UNIQUE SiZE" uniqueSizeHeader = "UNIQUE SIZE"
) )
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct. // 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() return ctx.parseFormat()
} }
//
// NewDiskUsageFormat returns a format for rendering an DiskUsageContext // NewDiskUsageFormat returns a format for rendering an DiskUsageContext
func NewDiskUsageFormat(source string) Format { func NewDiskUsageFormat(source string, verbose bool) Format {
switch source { switch {
case TableFormatKey: case verbose && source == RawFormatKey:
format := defaultDiskUsageTableFormat format := `{{range .Images}}type: Image
return Format(format) ` + NewImageFormat(source, false, true) + `
case RawFormatKey: {{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}} format := `type: {{.Type}}
total: {{.TotalCount}} total: {{.TotalCount}}
active: {{.Active}} active: {{.Active}}
@@ -71,8 +75,9 @@ size: {{.Size}}
reclaimable: {{.Reclaimable}} reclaimable: {{.Reclaimable}}
` `
return Format(format) return Format(format)
default:
return Format(source)
} }
return Format(source)
} }
func (ctx *DiskUsageContext) Write() (err error) { func (ctx *DiskUsageContext) Write() (err error) {
@@ -129,14 +134,23 @@ func (ctx *DiskUsageContext) Write() (err error) {
return err return err
} }
func (ctx *DiskUsageContext) verboseWrite() error { type diskUsageContext struct {
// First images Images []*imageContext
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat) Containers []*containerContext
if err != nil { Volumes []*volumeContext
return err 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 { for _, i := range ctx.Images {
repo := "<none>" repo := "<none>"
tag := "<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, repo: repo,
tag: tag, tag: tag,
trunc: true, trunc: trunc,
i: *i, 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 return err
} }
} }
ctx.postFormat(tmpl, newImageContext()) ctx.postFormat(tmpl, newImageContext())
// Now containers
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat) tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
if err != nil { if err != nil {
return err return err
} }
for _, c := range ctx.Containers { ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
// Don't display the virtual size for _, c := range duc.Containers {
c.SizeRootFs = 0 if err := ctx.contextFormat(tmpl, c); err != nil {
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
if err != nil {
return err return err
} }
} }
ctx.postFormat(tmpl, newContainerContext()) ctx.postFormat(tmpl, newContainerContext())
// And volumes
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat) tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
if err != nil { if err != nil {
return err return err
} }
for _, v := range ctx.Volumes { ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil { for _, v := range duc.Volumes {
if err := ctx.contextFormat(tmpl, v); err != nil {
return err return err
} }
} }
ctx.postFormat(tmpl, newVolumeContext()) ctx.postFormat(tmpl, newVolumeContext())
// And build cache tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize))) if err != nil {
return err
t := template.Must(template.New("buildcache").Parse(defaultBuildCacheVerboseFormat))
for _, v := range ctx.BuildCache {
t.Execute(ctx.Output, *v)
} }
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 return nil
} }
@@ -416,7 +467,7 @@ func (c *diskUsageBuilderContext) Size() string {
func (c *diskUsageBuilderContext) Reclaimable() string { func (c *diskUsageBuilderContext) Reclaimable() string {
var inUseBytes int64 var inUseBytes int64
for _, bc := range c.buildCache { for _, bc := range c.buildCache {
if bc.InUse { if bc.InUse && !bc.Shared {
inUseBytes += bc.Size inUseBytes += bc.Size
} }
} }
+16 -7
View File
@@ -18,7 +18,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
{ {
DiskUsageContext{ DiskUsageContext{
Context: Context{ Context: Context{
Format: NewDiskUsageFormat("table"), Format: NewDiskUsageFormat("table", false),
}, },
Verbose: false}, Verbose: false},
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE `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: `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: 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: Local Volumes space usage:
@@ -44,8 +44,17 @@ VOLUME NAME LINKS SIZE
Build cache usage: 0B 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 // Errors
{ {
DiskUsageContext{ DiskUsageContext{
@@ -69,7 +78,7 @@ Build cache usage: 0B
{ {
DiskUsageContext{ DiskUsageContext{
Context: Context{ Context: Context{
Format: NewDiskUsageFormat("table"), Format: NewDiskUsageFormat("table", false),
}, },
}, },
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE `TYPE TOTAL ACTIVE SIZE RECLAIMABLE
@@ -82,7 +91,7 @@ Build Cache 0 0 0B
{ {
DiskUsageContext{ DiskUsageContext{
Context: Context{ 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")), string(golden.Get(t, "disk-usage-context-write-custom.golden")),
@@ -91,7 +100,7 @@ Build Cache 0 0 0B
{ {
DiskUsageContext{ DiskUsageContext{
Context: Context{ Context: Context{
Format: NewDiskUsageFormat("raw"), Format: NewDiskUsageFormat("raw", false),
}, },
}, },
string(golden.Get(t, "disk-usage-raw-format.golden")), string(golden.Get(t, "disk-usage-raw-format.golden")),
+3 -3
View File
@@ -1,7 +1,7 @@
package formatter package formatter
import ( import (
"github.com/docker/cli/internal/containerizedengine" clitypes "github.com/docker/cli/types"
) )
const ( const (
@@ -31,7 +31,7 @@ func NewUpdatesFormat(source string, quiet bool) Format {
} }
// UpdatesWrite writes the context // UpdatesWrite writes the context
func UpdatesWrite(ctx Context, availableUpdates []containerizedengine.Update) error { func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error {
render := func(format func(subContext subContext) error) error { render := func(format func(subContext subContext) error) error {
for _, update := range availableUpdates { for _, update := range availableUpdates {
updatesCtx := &updateContext{trunc: ctx.Trunc, u: update} updatesCtx := &updateContext{trunc: ctx.Trunc, u: update}
@@ -53,7 +53,7 @@ func UpdatesWrite(ctx Context, availableUpdates []containerizedengine.Update) er
type updateContext struct { type updateContext struct {
HeaderContext HeaderContext
trunc bool trunc bool
u containerizedengine.Update u clitypes.Update
} }
func (c *updateContext) MarshalJSON() ([]byte, error) { func (c *updateContext) MarshalJSON() ([]byte, error) {
+4 -4
View File
@@ -6,7 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/internal/containerizedengine" clitypes "github.com/docker/cli/types"
"gotest.tools/assert" "gotest.tools/assert"
is "gotest.tools/assert/cmp" is "gotest.tools/assert/cmp"
) )
@@ -84,7 +84,7 @@ version2
} }
for _, testcase := range cases { for _, testcase := range cases {
updates := []containerizedengine.Update{ updates := []clitypes.Update{
{Type: "updateType1", Version: "version1", Notes: "description 1"}, {Type: "updateType1", Version: "version1", Notes: "description 1"},
{Type: "updateType2", Version: "version2", Notes: "description 2"}, {Type: "updateType2", Version: "version2", Notes: "description 2"},
} }
@@ -100,7 +100,7 @@ version2
} }
func TestUpdateContextWriteJSON(t *testing.T) { func TestUpdateContextWriteJSON(t *testing.T) {
updates := []containerizedengine.Update{ updates := []clitypes.Update{
{Type: "updateType1", Version: "version1", Notes: "note1"}, {Type: "updateType1", Version: "version1", Notes: "note1"},
{Type: "updateType2", Version: "version2", Notes: "note2"}, {Type: "updateType2", Version: "version2", Notes: "note2"},
} }
@@ -124,7 +124,7 @@ func TestUpdateContextWriteJSON(t *testing.T) {
} }
func TestUpdateContextWriteJSONField(t *testing.T) { func TestUpdateContextWriteJSONField(t *testing.T) {
updates := []containerizedengine.Update{ updates := []clitypes.Update{
{Type: "updateType1", Version: "version1"}, {Type: "updateType1", Version: "version1"},
{Type: "updateType2", Version: "version2"}, {Type: "updateType2", Version: "version2"},
} }
+17 -13
View File
@@ -13,7 +13,6 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strconv"
"strings" "strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@@ -73,6 +72,7 @@ type buildOptions struct {
platform string platform string
untrusted bool untrusted bool
secrets []string secrets []string
ssh []string
} }
// dockerfileFromStdin returns true when the user specified that the Dockerfile // dockerfileFromStdin returns true when the user specified that the Dockerfile
@@ -136,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.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.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.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.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.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
flags.SetAnnotation("network", "version", []string{"1.25"}) flags.SetAnnotation("network", "version", []string{"1.25"})
@@ -153,11 +155,18 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context") flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
flags.SetAnnotation("stream", "experimental", nil) flags.SetAnnotation("stream", "experimental", nil)
flags.SetAnnotation("stream", "version", []string{"1.31"}) flags.SetAnnotation("stream", "version", []string{"1.31"})
flags.SetAnnotation("stream", "no-buildkit", nil)
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (only if BuildKit enabled) (auto, plain, tty). Use plain to show container output") 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.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", "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 return cmd
} }
@@ -179,22 +188,17 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
// nolint: gocyclo // nolint: gocyclo
func runBuild(dockerCli command.Cli, options buildOptions) error { func runBuild(dockerCli command.Cli, options buildOptions) error {
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); buildkitEnv != "" { buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
enableBuildkit, err := strconv.ParseBool(buildkitEnv) if err != nil {
if err != nil { return err
return errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value") }
} if buildkitEnabled {
if enableBuildkit {
return runBuildBuildKit(dockerCli, options)
}
} else if dockerCli.ServerInfo().BuildkitVersion == types.BuilderBuildKit {
return runBuildBuildKit(dockerCli, options) return runBuildBuildKit(dockerCli, options)
} }
var ( var (
buildCtx io.ReadCloser buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser dockerfileCtx io.ReadCloser
err error
contextDir string contextDir string
tempDir string tempDir string
relDockerfile string relDockerfile string
@@ -346,7 +350,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
buildCtx = dockerfileCtx buildCtx = dockerfileCtx
} }
s, err := trySession(dockerCli, contextDir) s, err := trySession(dockerCli, contextDir, true)
if err != nil { if err != nil {
return err return err
} }
+34 -3
View File
@@ -27,10 +27,11 @@ import (
"github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/session/filesync" "github.com/moby/buildkit/session/filesync"
"github.com/moby/buildkit/session/secrets/secretsprovider" "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/appcontext"
"github.com/moby/buildkit/util/progress/progressui" "github.com/moby/buildkit/util/progress/progressui"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tonistiigi/fsutil" fsutiltypes "github.com/tonistiigi/fsutil/types"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@@ -42,7 +43,7 @@ var errDockerfileConflict = errors.New("ambiguous Dockerfile source: both stdin
func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error { func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
ctx := appcontext.Context() ctx := appcontext.Context()
s, err := trySession(dockerCli, options.context) s, err := trySession(dockerCli, options.context, false)
if err != nil { if err != nil {
return err return err
} }
@@ -138,6 +139,13 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
} }
s.Allow(sp) 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) eg, ctx := errgroup.WithContext(ctx)
@@ -291,7 +299,7 @@ func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, opt
return err return err
} }
func resetUIDAndGID(s *fsutil.Stat) bool { func resetUIDAndGID(s *fsutiltypes.Stat) bool {
s.Uid = 0 s.Uid = 0
s.Gid = 0 s.Gid = 0
return true return true
@@ -408,3 +416,26 @@ func parseSecret(value string) (*secretsprovider.FileSource, error) {
} }
return &fs, nil 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
}
+4 -4
View File
@@ -27,16 +27,16 @@ import (
const clientSessionRemote = "client-session" const clientSessionRemote = "client-session"
func isSessionSupported(dockerCli command.Cli) bool { func isSessionSupported(dockerCli command.Cli, forStream bool) bool {
if versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") { if !forStream && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") {
return true return true
} }
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31") 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 var s *session.Session
if isSessionSupported(dockerCli) { if isSessionSupported(dockerCli, forStream) {
sharedKey, err := getBuildSharedKey(contextDir) sharedKey, err := getBuildSharedKey(contextDir)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to get build shared key") return nil, errors.Wrap(err, "failed to get build shared key")
+5 -2
View File
@@ -19,6 +19,7 @@ type importOptions struct {
reference string reference string
changes dockeropts.ListOpts changes dockeropts.ListOpts
message string message string
platform string
} }
// NewImportCommand creates a new `docker import` command // NewImportCommand creates a new `docker import` command
@@ -43,6 +44,7 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command {
options.changes = dockeropts.NewListOpts(nil) options.changes = dockeropts.NewListOpts(nil)
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image") 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") flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
command.AddPlatformFlag(flags, &options.platform)
return cmd return cmd
} }
@@ -71,8 +73,9 @@ func runImport(dockerCli command.Cli, options importOptions) error {
} }
importOptions := types.ImageImportOptions{ importOptions := types.ImageImportOptions{
Message: options.message, Message: options.message,
Changes: options.changes.GetAll(), Changes: options.changes.GetAll(),
Platform: options.platform,
} }
clnt := dockerCli.Client() clnt := dockerCli.Client()
+29 -4
View File
@@ -3,11 +3,14 @@ package image
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units" units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -54,8 +57,24 @@ Are you sure you want to continue?`
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) { 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.Add("dangling", fmt.Sprintf("%v", !options.all))
pruneFilters = command.PruneFilters(dockerCli, pruneFilters) pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
@@ -73,14 +92,20 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
} }
if len(report.ImagesDeleted) > 0 { if len(report.ImagesDeleted) > 0 {
output = "Deleted Images:\n" var sb strings.Builder
sb.WriteString("Deleted Images:\n")
for _, st := range report.ImagesDeleted { for _, st := range report.ImagesDeleted {
if st.Untagged != "" { if st.Untagged != "" {
output += fmt.Sprintln("untagged:", st.Untagged) sb.WriteString("untagged: ")
sb.WriteString(st.Untagged)
sb.WriteByte('\n')
} else { } else {
output += fmt.Sprintln("deleted:", st.Deleted) sb.WriteString("deleted: ")
sb.WriteString(st.Deleted)
sb.WriteByte('\n')
} }
} }
output = sb.String()
spaceReclaimed = report.SpaceReclaimed spaceReclaimed = report.SpaceReclaimed
} }
+8
View File
@@ -70,6 +70,14 @@ func TestNewPruneCommandSuccess(t *testing.T) {
}, nil }, 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", name: "force-untagged",
args: []string{"--force"}, args: []string{"--force"},
@@ -0,0 +1 @@
Total reclaimed space: 0B
+1
View File
@@ -18,6 +18,7 @@ type osArch struct {
// list of valid os/arch values (see "Optional Environment Variables" section // list of valid os/arch values (see "Optional Environment Variables" section
// of https://golang.org/doc/install/source // of https://golang.org/doc/install/source
// Added linux/s390x as we know System z support already exists // 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{ var validOSArches = map[osArch]bool{
{os: "darwin", arch: "386"}: true, {os: "darwin", arch: "386"}: true,
{os: "darwin", arch: "amd64"}: true, {os: "darwin", arch: "amd64"}: true,
+1 -1
View File
@@ -70,7 +70,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e
// RunPrune calls the Network Prune API // RunPrune calls the Network Prune API
// This returns the amount of space reclaimed and a detailed output string // 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}) output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter})
return 0, output, err return 0, output, err
} }
+6
View File
@@ -302,6 +302,12 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
if task.Resources == nil { if task.Resources == nil {
task.Resources = &swarm.ResourceRequirements{} 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 return task.Resources
} }
+34 -2
View File
@@ -617,6 +617,38 @@ func TestUpdateIsolationValid(t *testing.T) {
// and that values are not updated are not reset to their default value // and that values are not updated are not reset to their default value
func TestUpdateLimitsReservations(t *testing.T) { func TestUpdateLimitsReservations(t *testing.T) {
spec := swarm.ServiceSpec{ 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{ TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{}, ContainerSpec: &swarm.ContainerSpec{},
Resources: &swarm.ResourceRequirements{ Resources: &swarm.ResourceRequirements{
@@ -632,8 +664,8 @@ func TestUpdateLimitsReservations(t *testing.T) {
}, },
} }
flags := newUpdateCommand(nil).Flags() flags = newUpdateCommand(nil).Flags()
err := flags.Set(flagLimitCPU, "2") err = flags.Set(flagLimitCPU, "2")
assert.NilError(t, err) assert.NilError(t, err)
err = flags.Set(flagReserveCPU, "2") err = flags.Set(flagReserveCPU, "2")
assert.NilError(t, err) assert.NilError(t, err)
+9 -7
View File
@@ -2,7 +2,6 @@ package system
import ( import (
"context" "context"
"errors"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@@ -38,10 +37,6 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
} }
func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error { func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
if opts.verbose && len(opts.format) != 0 {
return errors.New("the verbose and the format options conflict")
}
du, err := dockerCli.Client().DiskUsage(context.Background()) du, err := dockerCli.Client().DiskUsage(context.Background())
if err != nil { if err != nil {
return err return err
@@ -52,13 +47,20 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
format = formatter.TableFormatKey format = formatter.TableFormatKey
} }
var bsz int64
for _, bc := range du.BuildCache {
if !bc.Shared {
bsz += bc.Size
}
}
duCtx := formatter.DiskUsageContext{ duCtx := formatter.DiskUsageContext{
Context: formatter.Context{ Context: formatter.Context{
Output: dockerCli.Out(), Output: dockerCli.Out(),
Format: formatter.NewDiskUsageFormat(format), Format: formatter.NewDiskUsageFormat(format, opts.verbose),
}, },
LayersSize: du.LayersSize, LayersSize: du.LayersSize,
BuilderSize: du.BuilderSize, BuilderSize: bsz,
BuildCache: du.BuildCache, BuildCache: du.BuildCache,
Images: du.Images, Images: du.Images,
Containers: du.Containers, Containers: du.Containers,
+3
View File
@@ -204,6 +204,9 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
} }
fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled) fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled)
if info.ProductLicense != "" {
fmt.Fprintln(dockerCli.Out(), "Product License:", info.ProductLicense)
}
fmt.Fprint(dockerCli.Out(), "\n") fmt.Fprint(dockerCli.Out(), "\n")
printWarnings(dockerCli, info) printWarnings(dockerCli, info)
+13 -23
View File
@@ -2,12 +2,12 @@ package system
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"text/template" "text/template"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/builder"
"github.com/docker/cli/cli/command/container" "github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/network" "github.com/docker/cli/cli/command/network"
@@ -21,20 +21,21 @@ import (
type pruneOptions struct { type pruneOptions struct {
force bool force bool
all bool all bool
pruneBuildCache bool
pruneVolumes bool pruneVolumes bool
pruneBuildCache bool
filter opts.FilterOpt filter opts.FilterOpt
} }
// newPruneCommand creates a new cobra.Command for `docker prune` // newPruneCommand creates a new cobra.Command for `docker prune`
func newPruneCommand(dockerCli command.Cli) *cobra.Command { func newPruneCommand(dockerCli command.Cli) *cobra.Command {
options := pruneOptions{filter: opts.NewFilterOpt(), pruneBuildCache: true} options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "prune [OPTIONS]", Use: "prune [OPTIONS]",
Short: "Remove unused data", Short: "Remove unused data",
Args: cli.NoArgs, Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
options.pruneBuildCache = versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
return runPrune(dockerCli, options) return runPrune(dockerCli, options)
}, },
Annotations: map[string]string{"version": "1.25"}, Annotations: map[string]string{"version": "1.25"},
@@ -57,44 +58,29 @@ const confirmationTemplate = `WARNING! This will remove:
{{- end }} {{- end }}
Are you sure you want to continue?` Are you sure you want to continue?`
// runBuildCachePrune executes a prune command for build cache
func runBuildCachePrune(dockerCli command.Cli, _ opts.FilterOpt) (uint64, string, error) {
report, err := dockerCli.Client().BuildCachePrune(context.Background())
if err != nil {
return 0, "", err
}
return report.SpaceReclaimed, "", nil
}
func runPrune(dockerCli command.Cli, options pruneOptions) error { func runPrune(dockerCli command.Cli, options pruneOptions) error {
// TODO version this once "until" filter is supported for volumes // TODO version this once "until" filter is supported for volumes
if options.pruneVolumes && options.filter.Value().Contains("until") { if options.pruneVolumes && options.filter.Value().Contains("until") {
return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`) return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`)
} }
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.31") {
options.pruneBuildCache = false
}
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) { if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) {
return nil return nil
} }
imagePrune := func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) { pruneFuncs := []func(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error){
return image.RunPrune(dockerCli, options.all, options.filter)
}
pruneFuncs := []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){
container.RunPrune, container.RunPrune,
network.RunPrune, network.RunPrune,
} }
if options.pruneVolumes { if options.pruneVolumes {
pruneFuncs = append(pruneFuncs, volume.RunPrune) pruneFuncs = append(pruneFuncs, volume.RunPrune)
} }
pruneFuncs = append(pruneFuncs, imagePrune) pruneFuncs = append(pruneFuncs, image.RunPrune)
if options.pruneBuildCache { if options.pruneBuildCache {
pruneFuncs = append(pruneFuncs, runBuildCachePrune) pruneFuncs = append(pruneFuncs, builder.CachePrune)
} }
var spaceReclaimed uint64 var spaceReclaimed uint64
for _, pruneFn := range pruneFuncs { for _, pruneFn := range pruneFuncs {
spc, output, err := pruneFn(dockerCli, options.filter) spc, output, err := pruneFn(dockerCli, options.all, options.filter)
if err != nil { if err != nil {
return err return err
} }
@@ -126,7 +112,11 @@ func confirmationMessage(options pruneOptions) string {
warnings = append(warnings, "all dangling images") warnings = append(warnings, "all dangling images")
} }
if options.pruneBuildCache { if options.pruneBuildCache {
warnings = append(warnings, "all build cache") if options.all {
warnings = append(warnings, "all build cache")
} else {
warnings = append(warnings, "all dangling build cache")
}
} }
if len(options.filter.String()) > 0 { if len(options.filter.String()) > 0 {
warnings = append(warnings, "Elements to be pruned will be filtered with:") warnings = append(warnings, "Elements to be pruned will be filtered with:")
+1 -1
View File
@@ -148,7 +148,7 @@ func TestAddStageSigners(t *testing.T) {
assert.NilError(t, err) assert.NilError(t, err)
changeList := cl.List() changeList := cl.List()
assert.Check(t, is.Len(changeList, 4)) assert.Check(t, is.Len(changeList, 4))
// ordering is determinstic: // ordering is deterministic:
// first change is for targets/user key creation // first change is for targets/user key creation
newSignerKeyChange := changeList[0] newSignerKeyChange := changeList[0]
+1 -1
View File
@@ -73,6 +73,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
// RunPrune calls the Volume Prune API // RunPrune calls the Volume Prune API
// This returns the amount of space reclaimed and a detailed output string // 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}) return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
} }
+11 -5
View File
@@ -176,15 +176,21 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]extractedValu
// Soft default (fall back if unset or empty) // Soft default (fall back if unset or empty)
func softDefault(substitution string, mapping Mapping) (string, bool, error) { func softDefault(substitution string, mapping Mapping) (string, bool, error) {
return withDefault(substitution, mapping, "-:") sep := ":-"
if !strings.Contains(substitution, sep) {
return "", false, nil
}
name, defaultValue := partition(substitution, sep)
value, ok := mapping(name)
if !ok || value == "" {
return defaultValue, true, nil
}
return value, true, nil
} }
// Hard default (fall back if-and-only-if empty) // Hard default (fall back if-and-only-if empty)
func hardDefault(substitution string, mapping Mapping) (string, bool, error) { func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
return withDefault(substitution, mapping, "-") sep := "-"
}
func withDefault(substitution string, mapping Mapping, sep string) (string, bool, error) {
if !strings.Contains(substitution, sep) { if !strings.Contains(substitution, sep) {
return "", false, nil return "", false, nil
} }
+6
View File
@@ -78,6 +78,12 @@ func TestEmptyValueWithSoftDefault(t *testing.T) {
assert.Check(t, is.Equal("ok def", result)) assert.Check(t, is.Equal("ok def", result))
} }
func TestValueWithSoftDefault(t *testing.T) {
result, err := Substitute("ok ${FOO:-def}", defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal("ok first", result))
}
func TestEmptyValueWithHardDefault(t *testing.T) { func TestEmptyValueWithHardDefault(t *testing.T) {
result, err := Substitute("ok ${BAR-def}", defaultMapping) result, err := Substitute("ok ${BAR-def}", defaultMapping)
assert.NilError(t, err) assert.NilError(t, err)
+76 -13
View File
@@ -10,8 +10,10 @@ import (
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
"github.com/docker/cli/cli/connhelper/ssh" "github.com/docker/cli/cli/connhelper/ssh"
@@ -82,6 +84,9 @@ func newCommandConn(ctx context.Context, cmd string, args ...string) (net.Conn,
// commandConn implements net.Conn // commandConn implements net.Conn
type commandConn struct { type commandConn struct {
cmd *exec.Cmd cmd *exec.Cmd
cmdExited bool
cmdWaitErr error
cmdMutex sync.Mutex
stdin io.WriteCloser stdin io.WriteCloser
stdout io.ReadCloser stdout io.ReadCloser
stderrMu sync.Mutex stderrMu sync.Mutex
@@ -101,23 +106,75 @@ func (c *commandConn) killIfStdioClosed() error {
if !stdioClosed { if !stdioClosed {
return nil return nil
} }
var err error return c.kill()
// NOTE: maybe already killed here }
if err = c.cmd.Process.Kill(); err == nil {
err = c.cmd.Wait() // killAndWait tries sending SIGTERM to the process before sending SIGKILL.
func killAndWait(cmd *exec.Cmd) error {
var werr error
if runtime.GOOS != "windows" {
werrCh := make(chan error)
go func() { werrCh <- cmd.Wait() }()
cmd.Process.Signal(syscall.SIGTERM)
select {
case werr = <-werrCh:
case <-time.After(3 * time.Second):
cmd.Process.Kill()
werr = <-werrCh
}
} else {
cmd.Process.Kill()
werr = cmd.Wait()
} }
if err != nil { return werr
// err is typically "os: process already finished". }
// we check ProcessState here instead of `strings.Contains(err, "os: process already finished")`
if c.cmd.ProcessState.Exited() { // kill returns nil if the command terminated, regardless to the exit status.
err = nil func (c *commandConn) kill() error {
var werr error
c.cmdMutex.Lock()
if c.cmdExited {
werr = c.cmdWaitErr
} else {
werr = killAndWait(c.cmd)
c.cmdWaitErr = werr
c.cmdExited = true
}
c.cmdMutex.Unlock()
if werr == nil {
return nil
}
wExitErr, ok := werr.(*exec.ExitError)
if ok {
if wExitErr.ProcessState.Exited() {
return nil
} }
} }
return err return errors.Wrapf(werr, "connhelper: failed to wait")
} }
func (c *commandConn) onEOF(eof error) error { func (c *commandConn) onEOF(eof error) error {
werr := c.cmd.Wait() // when we got EOF, the command is going to be terminated
var werr error
c.cmdMutex.Lock()
if c.cmdExited {
werr = c.cmdWaitErr
} else {
werrCh := make(chan error)
go func() { werrCh <- c.cmd.Wait() }()
select {
case werr = <-werrCh:
c.cmdWaitErr = werr
c.cmdExited = true
case <-time.After(10 * time.Second):
c.cmdMutex.Unlock()
c.stderrMu.Lock()
stderr := c.stderr.String()
c.stderrMu.Unlock()
return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, eof, stderr)
}
}
c.cmdMutex.Unlock()
if werr == nil { if werr == nil {
return eof return eof
} }
@@ -148,7 +205,10 @@ func (c *commandConn) CloseRead() error {
c.stdioClosedMu.Lock() c.stdioClosedMu.Lock()
c.stdoutClosed = true c.stdoutClosed = true
c.stdioClosedMu.Unlock() c.stdioClosedMu.Unlock()
return c.killIfStdioClosed() if err := c.killIfStdioClosed(); err != nil {
logrus.Warnf("commandConn.CloseRead: %v", err)
}
return nil
} }
func (c *commandConn) Read(p []byte) (int, error) { func (c *commandConn) Read(p []byte) (int, error) {
@@ -167,7 +227,10 @@ func (c *commandConn) CloseWrite() error {
c.stdioClosedMu.Lock() c.stdioClosedMu.Lock()
c.stdinClosed = true c.stdinClosed = true
c.stdioClosedMu.Unlock() c.stdioClosedMu.Unlock()
return c.killIfStdioClosed() if err := c.killIfStdioClosed(); err != nil {
logrus.Warnf("commandConn.CloseWrite: %v", err)
}
return nil
} }
func (c *commandConn) Write(p []byte) (int, error) { func (c *commandConn) Write(p []byte) (int, error) {
+9 -3
View File
@@ -200,7 +200,7 @@ func continueOnError(err error) bool {
} }
func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error { func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
endpoints, err := allEndpoints(namedRef) endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
if err != nil { if err != nil {
return err return err
} }
@@ -262,12 +262,18 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
} }
// allEndpoints returns a list of endpoints ordered by priority (v2, https, v1). // allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
func allEndpoints(namedRef reference.Named) ([]registry.APIEndpoint, error) { func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
repoInfo, err := registry.ParseRepositoryInfo(namedRef) repoInfo, err := registry.ParseRepositoryInfo(namedRef)
if err != nil { if err != nil {
return nil, err return nil, err
} }
registryService, err := registry.NewService(registry.ServiceOptions{})
var serviceOpts registry.ServiceOptions
if insecure {
logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
}
registryService, err := registry.NewService(serviceOpts)
if err != nil { if err != nil {
return []registry.APIEndpoint{}, err return []registry.APIEndpoint{}, err
} }
+20 -4
View File
@@ -13,6 +13,7 @@ import (
cliconfig "github.com/docker/cli/cli/config" cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/debug" "github.com/docker/cli/cli/debug"
cliflags "github.com/docker/cli/cli/flags" cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/cli/internal/containerizedengine"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
@@ -32,6 +33,9 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
SilenceErrors: true, SilenceErrors: true,
TraverseChildren: true, TraverseChildren: true,
Args: noArgs, Args: noArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return command.ShowHelp(dockerCli.Err())(cmd, args)
},
PersistentPreRunE: func(cmd *cobra.Command, args []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// flags must be the top-level command flags, not cmd.Flags() // flags must be the top-level command flags, not cmd.Flags()
opts.Common.SetDefaultOptions(flags) opts.Common.SetDefaultOptions(flags)
@@ -99,8 +103,10 @@ func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.
ccmd.Println(err) ccmd.Println(err)
return return
} }
if err := hideUnsupportedFeatures(ccmd, dockerCli); err != nil {
hideUnsupportedFeatures(ccmd, dockerCli) ccmd.Println(err)
return
}
defaultHelpFunc(ccmd, args) defaultHelpFunc(ccmd, args)
}) })
} }
@@ -168,7 +174,7 @@ func main() {
stdin, stdout, stderr := term.StdStreams() stdin, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr) logrus.SetOutput(stderr)
dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled()) dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled(), containerizedengine.NewClient)
cmd := newDockerCommand(dockerCli) cmd := newDockerCommand(dockerCli)
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
@@ -234,15 +240,21 @@ func hideFeatureSubCommand(subcmd *cobra.Command, hasFeature bool, annotation st
} }
} }
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) { func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
clientVersion := details.Client().ClientVersion() clientVersion := details.Client().ClientVersion()
osType := details.ServerInfo().OSType osType := details.ServerInfo().OSType
hasExperimental := details.ServerInfo().HasExperimental hasExperimental := details.ServerInfo().HasExperimental
hasExperimentalCLI := details.ClientInfo().HasExperimental hasExperimentalCLI := details.ClientInfo().HasExperimental
hasBuildKit, err := command.BuildKitEnabled(details.ServerInfo())
if err != nil {
return err
}
cmd.Flags().VisitAll(func(f *pflag.Flag) { cmd.Flags().VisitAll(func(f *pflag.Flag) {
hideFeatureFlag(f, hasExperimental, "experimental") hideFeatureFlag(f, hasExperimental, "experimental")
hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI") hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
hideFeatureFlag(f, hasBuildKit, "buildkit")
hideFeatureFlag(f, !hasBuildKit, "no-buildkit")
// hide flags not supported by the server // hide flags not supported by the server
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) { if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
f.Hidden = true f.Hidden = true
@@ -258,6 +270,8 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
for _, subcmd := range cmd.Commands() { for _, subcmd := range cmd.Commands() {
hideFeatureSubCommand(subcmd, hasExperimental, "experimental") hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI") hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
hideFeatureSubCommand(subcmd, hasBuildKit, "buildkit")
hideFeatureSubCommand(subcmd, !hasBuildKit, "no-buildkit")
// hide subcommands not supported by the server // hide subcommands not supported by the server
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) { if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
subcmd.Hidden = true subcmd.Hidden = true
@@ -266,6 +280,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
subcmd.Hidden = true subcmd.Hidden = true
} }
} }
return nil
} }
// Checks if a command or one of its ancestors is in the list // Checks if a command or one of its ancestors is in the list
@@ -312,6 +327,7 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI { if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name)) errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
} }
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
} }
}) })
if len(errs) > 0 { if len(errs) > 0 {
+19 -1
View File
@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
@@ -26,8 +27,25 @@ func TestClientDebugEnabled(t *testing.T) {
func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) {
discard := ioutil.Discard discard := ioutil.Discard
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false)) cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false, nil))
cmd.SetArgs([]string{"help", "invalid"}) cmd.SetArgs([]string{"help", "invalid"})
err := cmd.Execute() err := cmd.Execute()
assert.Error(t, err, "unknown help topic: invalid") assert.Error(t, err, "unknown help topic: invalid")
} }
func TestExitStatusForInvalidSubcommand(t *testing.T) {
discard := ioutil.Discard
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false, nil))
cmd.SetArgs([]string{"invalid"})
err := cmd.Execute()
assert.Check(t, is.ErrorContains(err, "docker: 'invalid' is not a docker command."))
}
func TestVersion(t *testing.T) {
var b bytes.Buffer
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, &b, &b, false, nil))
cmd.SetArgs([]string{"--version"})
err := cmd.Execute()
assert.NilError(t, err)
assert.Check(t, is.Contains(b.String(), "Docker version"))
}
+171 -35
View File
@@ -563,23 +563,39 @@ __docker_append_to_completions() {
COMPREPLY=( ${COMPREPLY[@]/%/"$1"} ) COMPREPLY=( ${COMPREPLY[@]/%/"$1"} )
} }
# __docker_daemon_is_experimental tests whether the currently configured Docker # __docker_fetch_info fetches information about the configured Docker server and updates
# daemon runs in experimental mode. If so, the function exits with 0 (true). # several variables with the results.
# Otherwise, or if the result cannot be determined, the exit value is 1 (false). # The result is cached for the duration of one invocation of bash completion.
__docker_daemon_is_experimental() { __docker_fetch_info() {
[ "$(__docker_q version -f '{{.Server.Experimental}}')" = "true" ] if [ -z "$info_fetched" ] ; then
read -r client_experimental server_experimental server_os < <(__docker_q version -f '{{.Client.Experimental}} {{.Server.Experimental}} {{.Server.Os}}')
info_fetched=true
fi
} }
# __docker_daemon_os_is tests whether the currently configured Docker daemon runs # __docker_client_is_experimental tests whether the Docker cli is configured to support
# experimental features. If so, the function exits with 0 (true).
# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
__docker_client_is_experimental() {
__docker_fetch_info
[ "$client_experimental" = "true" ]
}
# __docker_server_is_experimental tests whether the currently configured Docker
# server runs in experimental mode. If so, the function exits with 0 (true).
# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
__docker_server_is_experimental() {
__docker_fetch_info
[ "$server_experimental" = "true" ]
}
# __docker_server_os_is tests whether the currently configured Docker server runs
# on the operating system passed in as the first argument. # on the operating system passed in as the first argument.
# It does so by querying the daemon for its OS. The result is cached for the duration
# of one invocation of bash completion so that this function can be used to test for
# several different operating systems without additional costs.
# Known operating systems: linux, windows. # Known operating systems: linux, windows.
__docker_daemon_os_is() { __docker_server_os_is() {
local expected_os="$1" local expected_os="$1"
local actual_os=${daemon_os=$(__docker_q version -f '{{.Server.Os}}')} __docker_fetch_info
[ "$actual_os" = "$expected_os" ] [ "$server_os" = "$expected_os" ]
} }
# __docker_stack_orchestrator_is tests whether the client is configured to use # __docker_stack_orchestrator_is tests whether the client is configured to use
@@ -865,6 +881,7 @@ __docker_complete_log_drivers() {
gelf gelf
journald journald
json-file json-file
local
logentries logentries
none none
splunk splunk
@@ -888,7 +905,8 @@ __docker_complete_log_options() {
local gcplogs_options="$common_options1 $common_options2 gcp-log-cmd gcp-meta-id gcp-meta-name gcp-meta-zone gcp-project" local gcplogs_options="$common_options1 $common_options2 gcp-log-cmd gcp-meta-id gcp-meta-name gcp-meta-zone gcp-project"
local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type gelf-tcp-max-reconnect gelf-tcp-reconnect-delay tag" local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type gelf-tcp-max-reconnect gelf-tcp-reconnect-delay tag"
local journald_options="$common_options1 $common_options2 tag" local journald_options="$common_options1 $common_options2 tag"
local json_file_options="$common_options1 $common_options2 max-file max-size" local json_file_options="$common_options1 $common_options2 compress max-file max-size"
local local_options="$common_options1 compress max-file max-size"
local logentries_options="$common_options1 $common_options2 line-only logentries-token tag" local logentries_options="$common_options1 $common_options2 line-only logentries-token tag"
local splunk_options="$common_options1 $common_options2 splunk-caname splunk-capath splunk-format splunk-gzip splunk-gzip-level splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag" local splunk_options="$common_options1 $common_options2 splunk-caname splunk-capath splunk-format splunk-gzip splunk-gzip-level splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag"
local syslog_options="$common_options1 $common_options2 syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag" local syslog_options="$common_options1 $common_options2 syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag"
@@ -917,6 +935,9 @@ __docker_complete_log_options() {
json-file) json-file)
COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) ) COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) )
;; ;;
local)
COMPREPLY=( $( compgen -W "$local_options" -S = -- "$cur" ) )
;;
logentries) logentries)
COMPREPLY=( $( compgen -W "$logentries_options" -S = -- "$cur" ) ) COMPREPLY=( $( compgen -W "$logentries_options" -S = -- "$cur" ) )
;; ;;
@@ -946,7 +967,7 @@ __docker_complete_log_driver_options() {
__docker_nospace __docker_nospace
return return
;; ;;
fluentd-async-connect) compress|fluentd-async-connect)
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) ) COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
return return
;; ;;
@@ -1128,7 +1149,8 @@ _docker_docker() {
*) *)
local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" ) local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" )
if [ "$cword" -eq "$counter" ]; then if [ "$cword" -eq "$counter" ]; then
__docker_daemon_is_experimental && commands+=(${experimental_commands[*]}) __docker_client_is_experimental && commands+=(${experimental_client_commands[*]})
__docker_server_is_experimental && commands+=(${experimental_server_commands[*]})
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) ) COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
fi fi
;; ;;
@@ -1837,14 +1859,14 @@ _docker_container_run_and_create() {
--volume -v --volume -v
--workdir -w --workdir -w
" "
__docker_daemon_os_is windows && options_with_args+=" __docker_server_os_is windows && options_with_args+="
--cpu-count --cpu-count
--cpu-percent --cpu-percent
--io-maxbandwidth --io-maxbandwidth
--io-maxiops --io-maxiops
--isolation --isolation
" "
__docker_daemon_is_experimental && options_with_args+=" __docker_server_is_experimental && options_with_args+="
--platform --platform
" "
@@ -1960,7 +1982,7 @@ _docker_container_run_and_create() {
return return
;; ;;
--isolation) --isolation)
if __docker_daemon_os_is windows ; then if __docker_server_os_is windows ; then
__docker_complete_isolation __docker_complete_isolation
return return
fi fi
@@ -2071,12 +2093,12 @@ _docker_container_start() {
__docker_complete_detach_keys && return __docker_complete_detach_keys && return
case "$prev" in case "$prev" in
--checkpoint) --checkpoint)
if __docker_daemon_is_experimental ; then if __docker_server_is_experimental ; then
return return
fi fi
;; ;;
--checkpoint-dir) --checkpoint-dir)
if __docker_daemon_is_experimental ; then if __docker_server_is_experimental ; then
_filedir -d _filedir -d
return return
fi fi
@@ -2086,7 +2108,7 @@ _docker_container_start() {
case "$cur" in case "$cur" in
-*) -*)
local options="--attach -a --detach-keys --help --interactive -i" local options="--attach -a --detach-keys --help --interactive -i"
__docker_daemon_is_experimental && options+=" --checkpoint --checkpoint-dir" __docker_server_is_experimental && options+=" --checkpoint --checkpoint-dir"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) ) COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;; ;;
*) *)
@@ -2449,7 +2471,7 @@ _docker_daemon() {
} }
_docker_deploy() { _docker_deploy() {
__docker_daemon_is_experimental && _docker_stack_deploy __docker_server_is_experimental && _docker_stack_deploy
} }
_docker_diff() { _docker_diff() {
@@ -2535,7 +2557,7 @@ _docker_image_build() {
--target --target
--ulimit --ulimit
" "
__docker_daemon_os_is windows && options_with_args+=" __docker_server_os_is windows && options_with_args+="
--isolation --isolation
" "
@@ -2549,7 +2571,7 @@ _docker_image_build() {
--quiet -q --quiet -q
--rm --rm
" "
if __docker_daemon_is_experimental ; then if __docker_server_is_experimental ; then
options_with_args+=" options_with_args+="
--platform --platform
" "
@@ -2584,7 +2606,7 @@ _docker_image_build() {
return return
;; ;;
--isolation) --isolation)
if __docker_daemon_os_is windows ; then if __docker_server_os_is windows ; then
__docker_complete_isolation __docker_complete_isolation
return return
fi fi
@@ -2664,14 +2686,16 @@ _docker_image_images() {
_docker_image_import() { _docker_image_import() {
case "$prev" in case "$prev" in
--change|-c|--message|-m) --change|-c|--message|-m|--platform)
return return
;; ;;
esac esac
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $( compgen -W "--change -c --help --message -m" -- "$cur" ) ) local options="--change -c --help --message -m"
__docker_server_is_experimental && options+=" --platform"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;; ;;
*) *)
local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m') local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m')
@@ -2779,7 +2803,7 @@ _docker_image_pull() {
case "$cur" in case "$cur" in
-*) -*)
local options="--all-tags -a --disable-content-trust=false --help" local options="--all-tags -a --disable-content-trust=false --help"
__docker_daemon_is_experimental && options+=" --platform" __docker_server_is_experimental && options+=" --platform"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) ) COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
;; ;;
@@ -3395,7 +3419,6 @@ _docker_service_update_and_create() {
local options_with_args=" local options_with_args="
--endpoint-mode --endpoint-mode
--entrypoint --entrypoint
--force
--health-cmd --health-cmd
--health-interval --health-interval
--health-retries --health-retries
@@ -3431,7 +3454,7 @@ _docker_service_update_and_create() {
--user -u --user -u
--workdir -w --workdir -w
" "
__docker_daemon_os_is windows && options_with_args+=" __docker_server_os_is windows && options_with_args+="
--credential-spec --credential-spec
" "
@@ -3520,6 +3543,10 @@ _docker_service_update_and_create() {
--secret-rm --secret-rm
" "
boolean_options="$boolean_options
--force
"
case "$prev" in case "$prev" in
--env-rm) --env-rm)
COMPREPLY=( $( compgen -e -- "$cur" ) ) COMPREPLY=( $( compgen -e -- "$cur" ) )
@@ -3817,6 +3844,109 @@ _docker_swarm_update() {
esac esac
} }
_docker_manifest() {
local subcommands="
annotate
create
inspect
push
"
__docker_subcommands "$subcommands" && return
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
;;
*)
COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
;;
esac
}
_docker_manifest_annotate() {
case "$prev" in
--arch)
COMPREPLY=( $( compgen -W "
386
amd64
arm
arm64
mips64
mips64le
ppc64le
s390x" -- "$cur" ) )
return
;;
--os)
COMPREPLY=( $( compgen -W "
darwin
dragonfly
freebsd
linux
netbsd
openbsd
plan9
solaris
windows" -- "$cur" ) )
return
;;
--os-features|--variant)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--arch --help --os --os-features --variant" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag "--arch|--os|--os-features|--variant" )
if [ "$cword" -eq "$counter" ] || [ "$cword" -eq "$((counter + 1))" ]; then
__docker_complete_images --force-tag --id
fi
;;
esac
}
_docker_manifest_create() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--amend -a --help --insecure" -- "$cur" ) )
;;
*)
__docker_complete_images --force-tag --id
;;
esac
}
_docker_manifest_inspect() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help --insecure --verbose -v" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag )
if [ "$cword" -eq "$counter" ] || [ "$cword" -eq "$((counter + 1))" ]; then
__docker_complete_images --force-tag --id
fi
;;
esac
}
_docker_manifest_push() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help --insecure --purge -p" -- "$cur" ) )
;;
*)
local counter=$( __docker_pos_first_nonflag )
if [ "$cword" -eq "$counter" ]; then
__docker_complete_images --force-tag --id
fi
;;
esac
}
_docker_node() { _docker_node() {
local subcommands=" local subcommands="
demote demote
@@ -4451,7 +4581,7 @@ _docker_stack_deploy() {
case "$cur" in case "$cur" in
-*) -*)
local options="--compose-file -c --help --orchestrator" local options="--compose-file -c --help --orchestrator"
__docker_daemon_is_experimental && __docker_stack_orchestrator_is swarm && options+=" --bundle-file" __docker_server_is_experimental && __docker_stack_orchestrator_is swarm && options+=" --bundle-file"
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace" __docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
__docker_stack_orchestrator_is swarm && options+=" --prune --resolve-image --with-registry-auth" __docker_stack_orchestrator_is swarm && options+=" --prune --resolve-image --with-registry-auth"
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) ) COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
@@ -5074,7 +5204,11 @@ _docker() {
wait wait
) )
local experimental_commands=( local experimental_client_commands=(
manifest
)
local experimental_server_commands=(
checkpoint checkpoint
deploy deploy
) )
@@ -5098,10 +5232,12 @@ _docker() {
--tlskey --tlskey
" "
local host config daemon_os # variables to cache server info, populated on demand for performance reasons
local info_fetched server_experimental server_os
# variables to cache client info, populated on demand for performance reasons # variables to cache client info, populated on demand for performance reasons
local stack_orchestrator_is_kubernetes stack_orchestrator_is_swarm local client_experimental stack_orchestrator_is_kubernetes stack_orchestrator_is_swarm
local host config
COMPREPLY=() COMPREPLY=()
local cur prev words cword local cur prev words cword
+4 -8
View File
@@ -105,7 +105,7 @@ shellcheck: build_shell_validate_image ## run shellcheck validation
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck
.PHONY: test-e2e ## run e2e tests .PHONY: test-e2e ## run e2e tests
test-e2e: test-e2e-non-experimental test-e2e-experimental test-e2e-containerized test-e2e: test-e2e-non-experimental test-e2e-experimental test-e2e-connhelper-ssh
.PHONY: test-e2e-experimental .PHONY: test-e2e-experimental
test-e2e-experimental: build_e2e_image test-e2e-experimental: build_e2e_image
@@ -115,13 +115,9 @@ test-e2e-experimental: build_e2e_image
test-e2e-non-experimental: build_e2e_image test-e2e-non-experimental: build_e2e_image
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME) docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
.PHONY: test-e2e-containerized .PHONY: test-e2e-connhelper-ssh
test-e2e-containerized: build_e2e_image test-e2e-connhelper-ssh: build_e2e_image
docker run --rm --privileged \ docker run -e TEST_CONNHELPER=ssh -e DOCKERD_EXPERIMENTAL=1 --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
-v /var/lib/docker \
-v /var/lib/containerd \
-v /lib/modules:/lib/modules \
$(E2E_IMAGE_NAME) /go/src/github.com/docker/cli/scripts/test/engine/entry
.PHONY: help .PHONY: help
help: ## print this help help: ## print this help
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.10.3-alpine FROM golang:1.10.8-alpine
RUN apk add -U git bash coreutils gcc musl-dev RUN apk add -U git bash coreutils gcc musl-dev
+1 -1
View File
@@ -1,3 +1,3 @@
FROM dockercore/golang-cross:1.10.3@sha256:7671b4ed357fda50124e5679d36c4c3206ded4d43f1d2e0ff3d120a1e2bf94d7 FROM dockercore/golang-cross:1.10.8@sha256:a93210f55a8137b4aa4b9f033ac7a80b66ab6337e98e7afb62abe93b4ad73cad
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1 ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
WORKDIR /go/src/github.com/docker/cli WORKDIR /go/src/github.com/docker/cli
+1 -1
View File
@@ -1,5 +1,5 @@
FROM golang:1.10.3-alpine FROM golang:1.10.8-alpine
RUN apk add -U git make bash coreutils ca-certificates curl RUN apk add -U git make bash coreutils ca-certificates curl
+2 -23
View File
@@ -1,4 +1,4 @@
ARG GO_VERSION=1.10.3 ARG GO_VERSION=1.10.8
FROM docker/containerd-shim-process:a4d1531 AS containerd-shim-process FROM docker/containerd-shim-process:a4d1531 AS containerd-shim-process
@@ -13,30 +13,9 @@ RUN apt-get update && apt-get install -y \
libapparmor-dev \ libapparmor-dev \
libseccomp-dev \ libseccomp-dev \
iptables \ iptables \
openssh-client \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out
#ARG CONTAINERD_VERSION=v1.1.2
#ARG CONTAINERD_VERSION=47a128d
#ARG CONTAINERD_VERSION=6c3e782f
ARG CONTAINERD_VERSION=65839a47a88b0a1c5dc34981f1741eccefc9f2b0
RUN git clone https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd && \
cd /go/src/github.com/containerd/containerd && \
git checkout ${CONTAINERD_VERSION} && \
make && \
make install
COPY e2eengine/config.toml /etc/containerd/config.toml
COPY --from=containerd-shim-process /bin/containerd-shim-process-v1 /bin/
# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out
ARG RUNC_VERSION=v1.0.0-rc5
RUN git clone https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc && \
cd /go/src/github.com/opencontainers/runc && \
git checkout ${RUNC_VERSION} && \
make && \
make install
ARG COMPOSE_VERSION=1.21.2 ARG COMPOSE_VERSION=1.21.2
RUN curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \ RUN curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \
&& chmod +x /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.10.3-alpine FROM golang:1.10.8-alpine
RUN apk add -U git RUN apk add -U git
+32 -1
View File
@@ -19,6 +19,37 @@ The following list of features are deprecated in Engine.
To learn more about Docker Engine's deprecation policy, To learn more about Docker Engine's deprecation policy,
see [Feature Deprecation Policy](https://docs.docker.com/engine/#feature-deprecation-policy). see [Feature Deprecation Policy](https://docs.docker.com/engine/#feature-deprecation-policy).
### Legacy "overlay" storage driver
**Deprecated in Release: v18.09.0**
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.
### device mapper storage driver
**Deprecated in Release: v18.09.0**
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.
### Reserved namespaces in engine labels ### Reserved namespaces in engine labels
**Deprecated in Release: v18.06.0** **Deprecated in Release: v18.06.0**
@@ -167,7 +198,7 @@ The docker login command is removing the ability to automatically register for a
**Target For Removal In Release: v17.06** **Target For Removal In Release: v17.06**
The flag `--security-opt` doesn't use the colon separator(`:`) anymore to divide keys and values, it uses the equal symbol(`=`) for consistency with other similar flags, like `--storage-opt`. The flag `--security-opt` doesn't use the colon separator (`:`) anymore to divide keys and values, it uses the equal symbol (`=`) for consistency with other similar flags, like `--storage-opt`.
### `/containers/(id or name)/copy` endpoint ### `/containers/(id or name)/copy` endpoint
+122 -2
View File
@@ -121,6 +121,28 @@ registries.
When you're done with your build, you're ready to look into [*Pushing a When you're done with your build, you're ready to look into [*Pushing a
repository to its registry*](https://docs.docker.com/engine/tutorials/dockerrepos/#/contributing-to-docker-hub). repository to its registry*](https://docs.docker.com/engine/tutorials/dockerrepos/#/contributing-to-docker-hub).
## BuildKit
Starting with version 18.09, Docker supports a new backend for executing your
builds that is provided by the [moby/buildkit](https://github.com/moby/buildkit)
project. The BuildKit backend provides many benefits compared to the old
implementation. For example, BuildKit can:
* Detect and skip executing unused build stages
* Parallelize building independent build stages
* Incrementally transfer only the changed files in your build context between builds
* Detect and skip transferring unused files in your build context
* Use external Dockerfile implementations with many new features
* Avoid side-effects with rest of the API (intermediate images and containers)
* Prioritize your build cache for automatic pruning
To use the BuildKit backend, you need to set an environment variable
`DOCKER_BUILDKIT=1` on the CLI before invoking `docker build`.
To learn about the experimental Dockerfile syntax available to BuildKit-based
builds [refer to the documentation in the BuildKit repository](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md).
## Format ## Format
Here is the format of the `Dockerfile`: Here is the format of the `Dockerfile`:
@@ -224,10 +246,64 @@ following lines are all treated identically:
# dIrEcTiVe=value # dIrEcTiVe=value
``` ```
The following parser directive is supported: The following parser directives are supported:
* `syntax`
* `escape` * `escape`
## syntax
# syntax=[remote image reference]
For example:
# syntax=docker/dockerfile
# syntax=docker/dockerfile:1.0
# syntax=docker.io/docker/dockerfile:1
# syntax=docker/dockerfile:1.0.0-experimental
# syntax=example.com/user/repo:tag@sha256:abcdef...
This feature is only enabled if the [BuildKit](#buildkit) backend is used.
The syntax directive defines the location of the Dockerfile builder that is used for
building the current Dockerfile. The BuildKit backend allows to seamlessly use
external implementations of builders that are distributed as Docker images and
execute inside a container sandbox environment.
Custom Dockerfile implementation allows you to:
- Automatically get bugfixes without updating the daemon
- Make sure all users are using the same implementation to build your Dockerfile
- Use the latest features without updating the daemon
- Try out new experimental or third-party features
### Official releases
Docker distributes official versions of the images that can be used for building
Dockerfiles under `docker/dockerfile` repository on Docker Hub. There are two
channels where new images are released: stable and experimental.
Stable channel follows semantic versioning. For example:
- docker/dockerfile:1.0.0 - only allow immutable version 1.0.0
- docker/dockerfile:1.0 - allow versions 1.0.*
- docker/dockerfile:1 - allow versions 1.*.*
- docker/dockerfile:latest - latest release on stable channel
The experimental channel uses incremental versioning with the major and minor
component from the stable channel on the time of the release. For example:
- docker/dockerfile:1.0.1-experimental - only allow immutable version 1.0.1-experimental
- docker/dockerfile:1.0-experimental - latest experimental releases after 1.0
- docker/dockerfile:experimental - latest release on experimental channel
You should choose a channel that best fits your needs. If you only want
bugfixes, you should use `docker/dockerfile:1.0`. If you want to benefit from
experimental features, you should use the experimental channel. If you are using
the experimental channel, newer releases may not be backwards compatible, so it
is recommended to use an immutable full version variant.
For master builds and nightly feature releases refer to the description in [the source repository](https://github.com/moby/buildkit/blob/master/README.md).
## escape ## escape
# escape=\ (backslash) # escape=\ (backslash)
@@ -1339,6 +1415,10 @@ The table below shows what command is executed for different `ENTRYPOINT` / `CMD
| **CMD ["p1_cmd", "p2_cmd"]** | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd | | **CMD ["p1_cmd", "p2_cmd"]** | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
| **CMD exec_cmd p1_cmd** | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd | | **CMD exec_cmd p1_cmd** | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
> **Note**: If `CMD` is defined from the base image, setting `ENTRYPOINT` will
> reset `CMD` to an empty value. In this scenario, `CMD` must be defined in the
> current image to have a value.
## VOLUME ## VOLUME
VOLUME ["/data"] VOLUME ["/data"]
@@ -1379,7 +1459,7 @@ Keep the following things in mind about volumes in the `Dockerfile`.
data within the volume after it has been declared, those changes will be discarded. data within the volume after it has been declared, those changes will be discarded.
- **JSON formatting**: The list is parsed as a JSON array. - **JSON formatting**: The list is parsed as a JSON array.
You must enclose words with double quotes (`"`)rather than single quotes (`'`). You must enclose words with double quotes (`"`) rather than single quotes (`'`).
- **The host directory is declared at container run-time**: The host directory - **The host directory is declared at container run-time**: The host directory
(the mountpoint) is, by its nature, host-dependent. This is to preserve image (the mountpoint) is, by its nature, host-dependent. This is to preserve image
@@ -1623,6 +1703,38 @@ RUN echo "Hello World"
When building this Dockerfile, the `HTTP_PROXY` is preserved in the When building this Dockerfile, the `HTTP_PROXY` is preserved in the
`docker history`, and changing its value invalidates the build cache. `docker history`, and changing its value invalidates the build cache.
### Automatic platform ARGs in the global scope
This feature is only available when using the [BuildKit](#buildkit) backend.
Docker predefines a set of `ARG` variables with information on the platform of
the node performing the build (build platform) and on the platform of the
resulting image (target platform). The target platform can be specified with
the `--platform` flag on `docker build`.
The following `ARG` variables are set automatically:
* `TARGETPLATFORM` - platform of the build result. Eg `linux/amd64`, `linux/arm/v7`, `windows/amd64`.
* `TARGETOS` - OS component of TARGETPLATFORM
* `TARGETARCH` - architecture component of TARGETPLATFORM
* `TARGETVARIANT` - variant component of TARGETPLATFORM
* `BUILDPLATFORM` - platform of the node performing the build.
* `BUILDOS` - OS component of BUILDPLATFORM
* `BUILDARCH` - OS component of BUILDPLATFORM
* `BUILDVARIANT` - OS component of BUILDPLATFORM
These arguments are defined in the global scope so are not automatically
available inside build stages or for your `RUN` commands. To expose one of
these arguments inside the build stage redefine it without value.
For example:
```Dockerfile
FROM alpine
ARG TARGETPLATFORM
RUN echo "I'm building for $TARGETPLATFORM"
```
### Impact on build caching ### Impact on build caching
`ARG` variables are not persisted into the built image as `ENV` variables are. `ARG` variables are not persisted into the built image as `ENV` variables are.
@@ -1931,6 +2043,14 @@ required such as `zsh`, `csh`, `tcsh` and others.
The `SHELL` feature was added in Docker 1.12. The `SHELL` feature was added in Docker 1.12.
## External implementation features
This feature is only available when using the [BuildKit](#buildkit) backend.
Docker build supports experimental features like cache mounts, build secrets and
ssh forwarding that are enabled by using an external implementation of the
builder with a syntax directive. To learn about these features, [refer to the documentation in BuildKit repository](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md).
## Dockerfile examples ## Dockerfile examples
Below you can see some examples of Dockerfile syntax. If you're interested in Below you can see some examples of Dockerfile syntax. If you're interested in
+4
View File
@@ -48,14 +48,18 @@ Options:
'<network-name>|<network-id>': connect to a user-defined network '<network-name>|<network-id>': connect to a user-defined network
--no-cache Do not use cache when building the image --no-cache Do not use cache when building the image
--pull Always attempt to pull a newer version of the image --pull Always attempt to pull a newer version of the image
--progress Set type of progress output (only if BuildKit enabled) (auto, plain, tty).
Use plain to show container output
-q, --quiet Suppress the build output and print image ID on success -q, --quiet Suppress the build output and print image ID on success
--rm Remove intermediate containers after a successful build (default true) --rm Remove intermediate containers after a successful build (default true)
--secret Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret"
--security-opt value Security Options (default []) --security-opt value Security Options (default [])
--shm-size bytes Size of /dev/shm --shm-size bytes Size of /dev/shm
The format is `<number><unit>`. `number` must be greater than `0`. The format is `<number><unit>`. `number` must be greater than `0`.
Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes),
or `g` (gigabytes). If you omit the unit, the system uses bytes. or `g` (gigabytes). If you omit the unit, the system uses bytes.
--squash Squash newly built layers into a single new layer (**Experimental Only**) --squash Squash newly built layers into a single new layer (**Experimental Only**)
--ssh SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|<id>[=<socket>|<key>[,<key>]])
-t, --tag value Name and optionally a tag in the 'name:tag' format (default []) -t, --tag value Name and optionally a tag in the 'name:tag' format (default [])
--target string Set the target build stage to build. --target string Set the target build stage to build.
--ulimit value Ulimit options (default []) --ulimit value Ulimit options (default [])
+2 -2
View File
@@ -85,7 +85,7 @@ Options:
--memory-reservation string Memory soft limit --memory-reservation string Memory soft limit
--memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap --memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--memory-swappiness int Tune container memory swappiness (0 to 100) (default -1) --memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
--mount value Attach a filesytem mount to the container (default []) --mount value Attach a filesystem mount to the container (default [])
--name string Assign a name to the container --name string Assign a name to the container
--network-alias value Add network-scoped alias for the container (default []) --network-alias value Add network-scoped alias for the container (default [])
--network string Connect a container to a network (default "default") --network string Connect a container to a network (default "default")
@@ -256,5 +256,5 @@ docker create --device-cgroup-rule='c 42:* rmw' -name my-container my-image
Then, a user could ask `udev` to execute a script that would `docker exec my-container mknod newDevX c 42 <minor>` Then, a user could ask `udev` to execute a script that would `docker exec my-container mknod newDevX c 42 <minor>`
the required device when it is added. the required device when it is added.
NOTE: initially present devices still need to be explicitely added to NOTE: initially present devices still need to be explicitly added to
the create/run command the create/run command
+32 -5
View File
@@ -191,7 +191,10 @@ $ docker -H ssh://example.com ps
``` ```
To use SSH connection, you need to set up `ssh` so that it can reach the To use SSH connection, you need to set up `ssh` so that it can reach the
remote host with public key authentication. remote host with public key authentication. Password authentication is not
supported. If your key is protected with passphrase, you need to set up
`ssh-agent`.
Also, you need to have `docker` binary 18.09 or later on the daemon host. Also, you need to have `docker` binary 18.09 or later on the daemon host.
#### Bind Docker to another host/port or a Unix socket #### Bind Docker to another host/port or a Unix socket
@@ -1297,12 +1300,18 @@ This is a full example of the allowed configuration options on Linux:
"exec-opts": [], "exec-opts": [],
"exec-root": "", "exec-root": "",
"experimental": false, "experimental": false,
"features": {},
"storage-driver": "", "storage-driver": "",
"storage-opts": [], "storage-opts": [],
"labels": [], "labels": [],
"live-restore": true, "live-restore": true,
"log-driver": "", "log-driver": "json-file",
"log-opts": {}, "log-opts": {
"max-size": "10m",
"max-files":"5",
"labels": "somelabel",
"env": "os,customer"
},
"mtu": 0, "mtu": 0,
"pidfile": "", "pidfile": "",
"cluster-store": "", "cluster-store": "",
@@ -1326,7 +1335,13 @@ This is a full example of the allowed configuration options on Linux:
"userns-remap": "", "userns-remap": "",
"group": "", "group": "",
"cgroup-parent": "", "cgroup-parent": "",
"default-ulimits": {}, "default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 64000,
"Soft": 64000
}
},
"init": false, "init": false,
"init-path": "/usr/libexec/docker-init", "init-path": "/usr/libexec/docker-init",
"ipv6": false, "ipv6": false,
@@ -1392,6 +1407,7 @@ This is a full example of the allowed configuration options on Windows:
"dns-search": [], "dns-search": [],
"exec-opts": [], "exec-opts": [],
"experimental": false, "experimental": false,
"features":{},
"storage-driver": "", "storage-driver": "",
"storage-opts": [], "storage-opts": [],
"labels": [], "labels": [],
@@ -1422,6 +1438,16 @@ This is a full example of the allowed configuration options on Windows:
} }
``` ```
#### Feature options
The optional field `features` in `daemon.json` allows users to enable or disable specific
daemon features. For example, `{"features":{"buildkit": true}}` enables `buildkit` as the
default docker image builder.
The list of currently supported feature options:
- `buildkit`: It enables `buildkit` as default builder when set to `true` or disables it by
`false`. Note that if this option is not explicitly set in the daemon config file, then it
is up to the cli to determine which builder to invoke.
#### Configuration reload behavior #### Configuration reload behavior
Some options can be reconfigured when the daemon is running without requiring Some options can be reconfigured when the daemon is running without requiring
@@ -1446,11 +1472,12 @@ The list of currently supported options that can be reconfigured is this:
the runtime shipped with the official docker packages. the runtime shipped with the official docker packages.
- `runtimes`: it updates the list of available OCI runtimes that can - `runtimes`: it updates the list of available OCI runtimes that can
be used to run containers. be used to run containers.
- `authorization-plugin`: specifies the authorization plugins to use. - `authorization-plugin`: it specifies the authorization plugins to use.
- `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries. - `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries.
- `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config. - `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config.
- `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config. - `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config.
- `shutdown-timeout`: it replaces the daemon's existing configuration timeout with a new timeout for shutting down all containers. - `shutdown-timeout`: it replaces the daemon's existing configuration timeout with a new timeout for shutting down all containers.
- `features`: it explicitly enables or disables specific features.
Updating and reloading the cluster configurations such as `--cluster-store`, Updating and reloading the cluster configurations such as `--cluster-store`,
`--cluster-advertise` and `--cluster-store-opts` will take effect only if `--cluster-advertise` and `--cluster-store-opts` will take effect only if
+9
View File
@@ -24,6 +24,7 @@ Options:
-c, --change value Apply Dockerfile instruction to the created image (default []) -c, --change value Apply Dockerfile instruction to the created image (default [])
--help Print usage --help Print usage
-m, --message string Set commit message for imported image -m, --message string Set commit message for imported image
--platform string Set platform if server is multi-platform capable
``` ```
## Description ## Description
@@ -87,3 +88,11 @@ Note the `sudo` in this example – you must preserve
the ownership of the files (especially root ownership) during the the ownership of the files (especially root ownership) during the
archiving with tar. If you are not root (or the sudo command) when you archiving with tar. If you are not root (or the sudo command) when you
tar, then the ownerships might not get preserved. tar, then the ownerships might not get preserved.
## When the daemon supports multiple operating systems
If the daemon supports multiple operating systems, and the image being imported
does not match the default operating system, it may be necessary to add
`--platform`. This would be necessary when importing a Linux image into a Windows
daemon.
# docker import --platform=linux .\linuximage.tar
+9 -3
View File
@@ -85,6 +85,8 @@ you can download them from:
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases - Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
- [pass](https://www.passwordstore.org/): https://github.com/docker/docker-credential-helpers/releases - [pass](https://www.passwordstore.org/): https://github.com/docker/docker-credential-helpers/releases
#### Configure the credentials store
You need to specify the credentials store in `$HOME/.docker/config.json` You need to specify the credentials store in `$HOME/.docker/config.json`
to tell the docker engine to use it. The value of the config property should be to tell the docker engine to use it. The value of the config property should be
the suffix of the program to use (i.e. everything after `docker-credential-`). the suffix of the program to use (i.e. everything after `docker-credential-`).
@@ -99,7 +101,7 @@ For example, to use `docker-credential-osxkeychain`:
If you are currently logged in, run `docker logout` to remove If you are currently logged in, run `docker logout` to remove
the credentials from the file and run `docker login` again. the credentials from the file and run `docker login` again.
### Default behavior #### Default behavior
By default, Docker looks for the native binary on each of the platforms, i.e. By default, Docker looks for the native binary on each of the platforms, i.e.
"osxkeychain" on macOS, "wincred" on windows, and "pass" on Linux. A special "osxkeychain" on macOS, "wincred" on windows, and "pass" on Linux. A special
@@ -108,7 +110,7 @@ it cannot find the "pass" binary. If none of these binaries are present, it
stores the credentials (i.e. password) in base64 encoding in the config files stores the credentials (i.e. password) in base64 encoding in the config files
described above. described above.
### Credential helper protocol #### Credential helper protocol
Credential helpers can be any program or script that follows a very simple protocol. Credential helpers can be any program or script that follows a very simple protocol.
This protocol is heavily inspired by Git, but it differs in the information shared. This protocol is heavily inspired by Git, but it differs in the information shared.
@@ -162,7 +164,7 @@ designated programs to handle credentials for *specific registries*. The default
credential store (`credsStore` or the config file itself) will not be used for credential store (`credsStore` or the config file itself) will not be used for
operations concerning credentials of the specified registries. operations concerning credentials of the specified registries.
### Logging out #### Configure credential helpers
If you are currently logged in, run `docker logout` to remove If you are currently logged in, run `docker logout` to remove
the credentials from the default store. the credentials from the default store.
@@ -182,3 +184,7 @@ For example:
} }
} }
``` ```
## Related commands
* [logout](logout.md)
+4
View File
@@ -30,3 +30,7 @@ Options:
```bash ```bash
$ docker logout localhost:8080 $ docker logout localhost:8080
``` ```
## Related commands
* [login](login.md)
+3 -3
View File
@@ -46,11 +46,11 @@ $ docker rmi fd484f19954f
Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force
2013/12/11 05:47:16 Error: failed to remove one or more images 2013/12/11 05:47:16 Error: failed to remove one or more images
$ docker rmi test1 $ docker rmi test1:latest
Untagged: test1:latest Untagged: test1:latest
$ docker rmi test2 $ docker rmi test2:latest
Untagged: test2:latest Untagged: test2:latest
@@ -60,7 +60,7 @@ $ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE REPOSITORY TAG IMAGE ID CREATED SIZE
test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
$ docker rmi test $ docker rmi test:latest
Untagged: test:latest Untagged: test:latest
Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
+1 -1
View File
@@ -418,7 +418,7 @@ $ docker run -l my-label --label com.example.foo=bar ubuntu bash
``` ```
The `my-label` key doesn't specify a value so the label defaults to an empty The `my-label` key doesn't specify a value so the label defaults to an empty
string(`""`). To add multiple labels, repeat the label flag (`-l` or `--label`). string (`""`). To add multiple labels, repeat the label flag (`-l` or `--label`).
The `key=value` must be unique to avoid overwriting the label value. If you The `key=value` must be unique to avoid overwriting the label value. If you
specify labels with identical keys but different values, each subsequent value specify labels with identical keys but different values, each subsequent value
+2 -2
View File
@@ -1085,7 +1085,7 @@ per second from `/dev/sda`:
$ docker run -it --device-read-bps /dev/sda:1mb ubuntu $ docker run -it --device-read-bps /dev/sda:1mb ubuntu
The `--device-write-bps` flag limits the write rate (bytes per second)to a device. The `--device-write-bps` flag limits the write rate (bytes per second) to a device.
For example, this command creates a container and limits the write rate to `1mb` For example, this command creates a container and limits the write rate to `1mb`
per second for `/dev/sda`: per second for `/dev/sda`:
@@ -1555,7 +1555,7 @@ The example below mounts an empty tmpfs into the container with the `rw`,
If neither 'rw' or 'ro' is specified then the volume is mounted in If neither 'rw' or 'ro' is specified then the volume is mounted in
read-write mode. read-write mode.
The `nocopy` modes is used to disable automatic copying requested volume The `nocopy` mode is used to disable automatically copying the requested volume
path in the container to the volume storage location. path in the container to the volume storage location.
For named volumes, `copy` is the default mode. Copy modes are not supported For named volumes, `copy` is the default mode. Copy modes are not supported
for bind-mounted volumes. for bind-mounted volumes.
+19 -1
View File
@@ -19,9 +19,10 @@ const descriptionSourcePath = "docs/reference/commandline/"
func generateCliYaml(opts *options) error { func generateCliYaml(opts *options) error {
stdin, stdout, stderr := term.StdStreams() stdin, stdout, stderr := term.StdStreams()
dockerCli := command.NewDockerCli(stdin, stdout, stderr, false) dockerCli := command.NewDockerCli(stdin, stdout, stderr, false, nil)
cmd := &cobra.Command{Use: "docker"} cmd := &cobra.Command{Use: "docker"}
commands.AddCommands(cmd, dockerCli) commands.AddCommands(cmd, dockerCli)
disableFlagsInUseLine(cmd)
source := filepath.Join(opts.source, descriptionSourcePath) source := filepath.Join(opts.source, descriptionSourcePath)
if err := loadLongDescription(cmd, source); err != nil { if err := loadLongDescription(cmd, source); err != nil {
return err return err
@@ -31,6 +32,23 @@ func generateCliYaml(opts *options) error {
return GenYamlTree(cmd, opts.target) return GenYamlTree(cmd, opts.target)
} }
func disableFlagsInUseLine(cmd *cobra.Command) {
visitAll(cmd, func(ccmd *cobra.Command) {
// do not add a `[flags]` to the end of the usage line.
ccmd.DisableFlagsInUseLine = true
})
}
// visitAll will traverse all commands from the root.
// This is different from the VisitAll of cobra.Command where only parents
// are checked.
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
for _, cmd := range root.Commands() {
visitAll(cmd, fn)
}
fn(root)
}
func loadLongDescription(cmd *cobra.Command, path ...string) error { func loadLongDescription(cmd *cobra.Command, path ...string) error {
for _, cmd := range cmd.Commands() { for _, cmd := range cmd.Commands() {
if cmd.Name() == "" { if cmd.Name() == "" {
+9
View File
@@ -0,0 +1,9 @@
version: '2.1'
services:
engine:
build:
context: ./testdata
dockerfile: Dockerfile.connhelper-ssh
environment:
- TEST_CONNHELPER_SSH_ID_RSA_PUB
+14
View File
@@ -0,0 +1,14 @@
FROM docker:test-dind
RUN apk --no-cache add shadow openssh-server && \
groupadd -f docker && \
useradd -m penguin && \
usermod -aG docker penguin && \
usermod -p $(head -c32 /dev/urandom | base64) penguin && \
chsh -s /bin/sh penguin && \
ssh-keygen -A
# workaround: ssh session excludes /usr/local/bin from $PATH
RUN ln -s /usr/local/bin/docker /usr/bin/docker
COPY ./connhelper-ssh/entrypoint.sh /
EXPOSE 22
ENTRYPOINT ["/entrypoint.sh"]
# usage: docker run --privileged -e TEST_CONNHELPER_SSH_ID_RSA_PUB=$(cat ~/.ssh/id_rsa.pub) -p 22 $THIS_IMAGE
+8
View File
@@ -0,0 +1,8 @@
#!/bin/sh
set -ex
mkdir -m 0700 -p /home/penguin/.ssh
echo ${TEST_CONNHELPER_SSH_ID_RSA_PUB} > /home/penguin/.ssh/authorized_keys
chmod 0600 /home/penguin/.ssh/authorized_keys
chown -R penguin:penguin /home/penguin
/usr/sbin/sshd -E /var/log/sshd.log
exec dockerd-entrypoint.sh $@
-42
View File
@@ -1,42 +0,0 @@
package check
import (
"os"
"testing"
"github.com/docker/cli/e2eengine"
"gotest.tools/icmd"
)
func TestDockerEngineOnContainerdAltRootConfig(t *testing.T) {
defer func() {
err := e2eengine.CleanupEngine(t)
if err != nil {
t.Errorf("Failed to cleanup engine: %s", err)
}
}()
t.Log("First engine init")
// First init
result := icmd.RunCmd(icmd.Command("docker", "engine", "init", "--config-file", "/tmp/etc/docker/daemon.json"),
func(c *icmd.Cmd) {
c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
})
result.Assert(t, icmd.Expected{
Out: "Success! The docker engine is now running.",
Err: "",
ExitCode: 0,
})
// Make sure update doesn't blow up with alternate config path
t.Log("perform update")
// Now update and succeed
targetVersion := os.Getenv("VERSION")
result = icmd.RunCmd(icmd.Command("docker", "engine", "update", "--version", targetVersion))
result.Assert(t, icmd.Expected{
Out: "Success! The docker engine is now running.",
Err: "",
ExitCode: 0,
})
}
-14
View File
@@ -1,14 +0,0 @@
root = "/var/lib/containerd"
state = "/run/containerd"
oom_score = 0
[grpc]
address = "/run/containerd/containerd.sock"
uid = 0
gid = 0
[debug]
address = "/run/containerd/debug.sock"
uid = 0
gid = 0
level = "debug"
-85
View File
@@ -1,85 +0,0 @@
package multi
import (
"os"
"testing"
"github.com/docker/cli/e2eengine"
"gotest.tools/icmd"
)
func TestDockerEngineOnContainerdMultiTest(t *testing.T) {
defer func() {
err := e2eengine.CleanupEngine(t)
if err != nil {
t.Errorf("Failed to cleanup engine: %s", err)
}
}()
t.Log("Attempt engine init without experimental")
// First init
result := icmd.RunCmd(icmd.Command("docker", "engine", "init"),
func(c *icmd.Cmd) {
c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=disabled")
})
result.Assert(t, icmd.Expected{
Out: "",
Err: "docker engine init is only supported",
ExitCode: 1,
})
t.Log("First engine init")
// First init
result = icmd.RunCmd(icmd.Command("docker", "engine", "init"),
func(c *icmd.Cmd) {
c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
})
result.Assert(t, icmd.Expected{
Out: "Success! The docker engine is now running.",
Err: "",
ExitCode: 0,
})
t.Log("checking for updates")
// Check for updates
result = icmd.RunCmd(icmd.Command("docker", "engine", "check", "--downgrades", "--pre-releases"))
result.Assert(t, icmd.Expected{
Out: "VERSION",
Err: "",
ExitCode: 0,
})
t.Log("attempt second init (should fail)")
// Attempt to init a second time and fail
result = icmd.RunCmd(icmd.Command("docker", "engine", "init"),
func(c *icmd.Cmd) {
c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
})
result.Assert(t, icmd.Expected{
Out: "",
Err: "engine already present",
ExitCode: 1,
})
t.Log("perform update")
// Now update and succeed
targetVersion := os.Getenv("VERSION")
result = icmd.RunCmd(icmd.Command("docker", "engine", "update", "--version", targetVersion))
result.Assert(t, icmd.Expected{
Out: "Success! The docker engine is now running.",
Err: "",
ExitCode: 0,
})
t.Log("remove engine")
result = icmd.RunCmd(icmd.Command("docker", "engine", "rm"),
func(c *icmd.Cmd) {
c.Env = append(c.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
})
result.Assert(t, icmd.Expected{
Out: "",
Err: "",
ExitCode: 0,
})
}
-39
View File
@@ -1,39 +0,0 @@
package e2eengine
import (
"context"
"strings"
"testing"
"github.com/docker/cli/internal/containerizedengine"
)
// CleanupEngine ensures the local engine has been removed between testcases
func CleanupEngine(t *testing.T) error {
t.Log("doing engine cleanup")
ctx := context.Background()
client, err := containerizedengine.NewClient("")
if err != nil {
return err
}
// See if the engine exists first
engine, err := client.GetEngine(ctx)
if err != nil {
if strings.Contains(err.Error(), "not present") {
t.Log("engine was not detected, no cleanup to perform")
// Nothing to do, it's not defined
return nil
}
t.Logf("failed to lookup engine: %s", err)
// Any other error is not good...
return err
}
// TODO Consider nuking the docker dir too so there's no cached content between test cases
err = client.RemoveEngine(ctx, engine)
if err != nil {
t.Logf("Failed to remove engine: %s", err)
}
return err
}
+14 -144
View File
@@ -1,12 +1,9 @@
package containerizedengine package containerizedengine
import ( import (
"bytes"
"context" "context"
"syscall"
"github.com/containerd/containerd" "github.com/containerd/containerd"
containerdtypes "github.com/containerd/containerd/api/types"
"github.com/containerd/containerd/cio" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
@@ -14,7 +11,6 @@ import (
prototypes "github.com/gogo/protobuf/types" prototypes "github.com/gogo/protobuf/types"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runtime-spec/specs-go"
) )
type ( type (
@@ -25,6 +21,8 @@ type (
getImageFunc func(ctx context.Context, ref string) (containerd.Image, error) getImageFunc func(ctx context.Context, ref string) (containerd.Image, error)
contentStoreFunc func() content.Store contentStoreFunc func() content.Store
containerServiceFunc func() containers.Store containerServiceFunc func() containers.Store
installFunc func(context.Context, containerd.Image, ...containerd.InstallOpts) error
versionFunc func(ctx context.Context) (containerd.Version, error)
} }
fakeContainer struct { fakeContainer struct {
idFunc func() string idFunc func() string
@@ -49,30 +47,6 @@ type (
isUnpackedFunc func(context.Context, string) (bool, error) isUnpackedFunc func(context.Context, string) (bool, error)
contentStoreFunc func() content.Store contentStoreFunc func() content.Store
} }
fakeTask struct {
idFunc func() string
pidFunc func() uint32
startFunc func(context.Context) error
deleteFunc func(context.Context, ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error)
killFunc func(context.Context, syscall.Signal, ...containerd.KillOpts) error
waitFunc func(context.Context) (<-chan containerd.ExitStatus, error)
closeIOFunc func(context.Context, ...containerd.IOCloserOpts) error
resizeFunc func(ctx context.Context, w, h uint32) error
ioFunc func() cio.IO
statusFunc func(context.Context) (containerd.Status, error)
pauseFunc func(context.Context) error
resumeFunc func(context.Context) error
execFunc func(context.Context, string, *specs.Process, cio.Creator) (containerd.Process, error)
pidsFunc func(context.Context) ([]containerd.ProcessInfo, error)
checkpointFunc func(context.Context, ...containerd.CheckpointTaskOpts) (containerd.Image, error)
updateFunc func(context.Context, ...containerd.UpdateTaskOpts) error
loadProcessFunc func(context.Context, string, cio.Attach) (containerd.Process, error)
metricsFunc func(context.Context) (*containerdtypes.Metric, error)
}
testOutStream struct {
bytes.Buffer
}
) )
func (w *fakeContainerdClient) Containers(ctx context.Context, filters ...string) ([]containerd.Container, error) { func (w *fakeContainerdClient) Containers(ctx context.Context, filters ...string) ([]containerd.Container, error) {
@@ -114,6 +88,18 @@ func (w *fakeContainerdClient) ContainerService() containers.Store {
func (w *fakeContainerdClient) Close() error { func (w *fakeContainerdClient) Close() error {
return nil return nil
} }
func (w *fakeContainerdClient) Install(ctx context.Context, image containerd.Image, args ...containerd.InstallOpts) error {
if w.installFunc != nil {
return w.installFunc(ctx, image, args...)
}
return nil
}
func (w *fakeContainerdClient) Version(ctx context.Context) (containerd.Version, error) {
if w.versionFunc != nil {
return w.versionFunc(ctx)
}
return containerd.Version{}, nil
}
func (c *fakeContainer) ID() string { func (c *fakeContainer) ID() string {
if c.idFunc != nil { if c.idFunc != nil {
@@ -230,119 +216,3 @@ func (i *fakeImage) ContentStore() content.Store {
} }
return nil return nil
} }
func (t *fakeTask) ID() string {
if t.idFunc != nil {
return t.idFunc()
}
return ""
}
func (t *fakeTask) Pid() uint32 {
if t.pidFunc != nil {
return t.pidFunc()
}
return 0
}
func (t *fakeTask) Start(ctx context.Context) error {
if t.startFunc != nil {
return t.startFunc(ctx)
}
return nil
}
func (t *fakeTask) Delete(ctx context.Context, opts ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error) {
if t.deleteFunc != nil {
return t.deleteFunc(ctx, opts...)
}
return nil, nil
}
func (t *fakeTask) Kill(ctx context.Context, signal syscall.Signal, opts ...containerd.KillOpts) error {
if t.killFunc != nil {
return t.killFunc(ctx, signal, opts...)
}
return nil
}
func (t *fakeTask) Wait(ctx context.Context) (<-chan containerd.ExitStatus, error) {
if t.waitFunc != nil {
return t.waitFunc(ctx)
}
return nil, nil
}
func (t *fakeTask) CloseIO(ctx context.Context, opts ...containerd.IOCloserOpts) error {
if t.closeIOFunc != nil {
return t.closeIOFunc(ctx, opts...)
}
return nil
}
func (t *fakeTask) Resize(ctx context.Context, w, h uint32) error {
if t.resizeFunc != nil {
return t.resizeFunc(ctx, w, h)
}
return nil
}
func (t *fakeTask) IO() cio.IO {
if t.ioFunc != nil {
return t.ioFunc()
}
return nil
}
func (t *fakeTask) Status(ctx context.Context) (containerd.Status, error) {
if t.statusFunc != nil {
return t.statusFunc(ctx)
}
return containerd.Status{}, nil
}
func (t *fakeTask) Pause(ctx context.Context) error {
if t.pauseFunc != nil {
return t.pauseFunc(ctx)
}
return nil
}
func (t *fakeTask) Resume(ctx context.Context) error {
if t.resumeFunc != nil {
return t.resumeFunc(ctx)
}
return nil
}
func (t *fakeTask) Exec(ctx context.Context, cmd string, proc *specs.Process, ioc cio.Creator) (containerd.Process, error) {
if t.execFunc != nil {
return t.execFunc(ctx, cmd, proc, ioc)
}
return nil, nil
}
func (t *fakeTask) Pids(ctx context.Context) ([]containerd.ProcessInfo, error) {
if t.pidsFunc != nil {
return t.pidsFunc(ctx)
}
return nil, nil
}
func (t *fakeTask) Checkpoint(ctx context.Context, opts ...containerd.CheckpointTaskOpts) (containerd.Image, error) {
if t.checkpointFunc != nil {
return t.checkpointFunc(ctx, opts...)
}
return nil, nil
}
func (t *fakeTask) Update(ctx context.Context, opts ...containerd.UpdateTaskOpts) error {
if t.updateFunc != nil {
return t.updateFunc(ctx, opts...)
}
return nil
}
func (t *fakeTask) LoadProcess(ctx context.Context, name string, attach cio.Attach) (containerd.Process, error) {
if t.loadProcessFunc != nil {
return t.loadProcessFunc(ctx, name, attach)
}
return nil, nil
}
func (t *fakeTask) Metrics(ctx context.Context) (*containerdtypes.Metric, error) {
if t.metricsFunc != nil {
return t.metricsFunc(ctx)
}
return nil, nil
}
func (o *testOutStream) FD() uintptr {
return 0
}
func (o *testOutStream) IsTerminal() bool {
return false
}
+5 -4
View File
@@ -7,6 +7,7 @@ import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/remotes/docker"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/jsonmessage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -15,7 +16,7 @@ import (
// NewClient returns a new containerizedengine client // NewClient returns a new containerizedengine client
// This client can be used to manage the lifecycle of // This client can be used to manage the lifecycle of
// dockerd running as a container on containerd. // dockerd running as a container on containerd.
func NewClient(sockPath string) (Client, error) { func NewClient(sockPath string) (clitypes.ContainerizedClient, error) {
if sockPath == "" { if sockPath == "" {
sockPath = containerdSockPath sockPath = containerdSockPath
} }
@@ -23,17 +24,17 @@ func NewClient(sockPath string) (Client, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return baseClient{ return &baseClient{
cclient: cclient, cclient: cclient,
}, nil }, nil
} }
// Close will close the underlying clients // Close will close the underlying clients
func (c baseClient) Close() error { func (c *baseClient) Close() error {
return c.cclient.Close() return c.cclient.Close()
} }
func (c baseClient) pullWithAuth(ctx context.Context, imageName string, out OutStream, func (c *baseClient) pullWithAuth(ctx context.Context, imageName string, out clitypes.OutStream,
authConfig *types.AuthConfig) (containerd.Image, error) { authConfig *types.AuthConfig) (containerd.Image, error) {
resolver := docker.NewResolver(docker.ResolverOptions{ resolver := docker.NewResolver(docker.ResolverOptions{
@@ -1,11 +1,13 @@
package containerizedengine package containerizedengine
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"testing" "testing"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"gotest.tools/assert" "gotest.tools/assert"
) )
@@ -22,7 +24,7 @@ func TestPullWithAuthPullFail(t *testing.T) {
} }
imageName := "testnamegoeshere" imageName := "testnamegoeshere"
_, err := client.pullWithAuth(ctx, imageName, &testOutStream{}, &types.AuthConfig{}) _, err := client.pullWithAuth(ctx, imageName, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "pull failure") assert.ErrorContains(t, err, "pull failure")
} }
@@ -38,6 +40,6 @@ func TestPullWithAuthPullPass(t *testing.T) {
} }
imageName := "testnamegoeshere" imageName := "testnamegoeshere"
_, err := client.pullWithAuth(ctx, imageName, &testOutStream{}, &types.AuthConfig{}) _, err := client.pullWithAuth(ctx, imageName, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.NilError(t, err) assert.NilError(t, err)
} }
-261
View File
@@ -1,261 +0,0 @@
package containerizedengine
import (
"context"
"fmt"
"io"
"strings"
"syscall"
"time"
"github.com/containerd/containerd"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/runtime/restart"
"github.com/docker/cli/internal/pkg/containerized"
"github.com/docker/docker/api/types"
"github.com/pkg/errors"
)
// InitEngine is the main entrypoint for `docker engine init`
func (c baseClient) InitEngine(ctx context.Context, opts EngineInitOptions, out OutStream,
authConfig *types.AuthConfig, healthfn func(context.Context) error) error {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
// Verify engine isn't already running
_, err := c.GetEngine(ctx)
if err == nil {
return ErrEngineAlreadyPresent
} else if err != ErrEngineNotPresent {
return err
}
imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
// Look for desired image
_, err = c.cclient.GetImage(ctx, imageName)
if err != nil {
if errdefs.IsNotFound(err) {
_, err = c.pullWithAuth(ctx, imageName, out, authConfig)
if err != nil {
return errors.Wrapf(err, "unable to pull image %s", imageName)
}
} else {
return errors.Wrapf(err, "unable to check for image %s", imageName)
}
}
// Spin up the engine
err = c.startEngineOnContainerd(ctx, imageName, opts.ConfigFile)
if err != nil {
return errors.Wrap(err, "failed to create docker daemon")
}
// Wait for the daemon to start, verify it's responsive
fmt.Fprintf(out, "Waiting for engine to start... ")
ctx, cancel := context.WithTimeout(ctx, engineWaitTimeout)
defer cancel()
if err := c.waitForEngine(ctx, out, healthfn); err != nil {
// TODO once we have the logging strategy sorted out
// this should likely gather the last few lines of logs to report
// why the daemon failed to initialize
return errors.Wrap(err, "failed to start docker daemon")
}
fmt.Fprintf(out, "Success! The docker engine is now running.\n")
return nil
}
// GetEngine will return the containerd container running the engine (or error)
func (c baseClient) GetEngine(ctx context.Context) (containerd.Container, error) {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
containers, err := c.cclient.Containers(ctx, "id=="+engineContainerName)
if err != nil {
return nil, err
}
if len(containers) == 0 {
return nil, ErrEngineNotPresent
}
return containers[0], nil
}
// getEngineImage will return the current image used by the engine
func (c baseClient) getEngineImage(engine containerd.Container) (string, error) {
ctx := namespaces.WithNamespace(context.Background(), engineNamespace)
image, err := engine.Image(ctx)
if err != nil {
return "", err
}
return image.Name(), nil
}
// getEngineConfigFilePath will extract the config file location from the engine flags
func (c baseClient) getEngineConfigFilePath(ctx context.Context, engine containerd.Container) (string, error) {
spec, err := engine.Spec(ctx)
configFile := ""
if err != nil {
return configFile, err
}
for i := 0; i < len(spec.Process.Args); i++ {
arg := spec.Process.Args[i]
if strings.HasPrefix(arg, "--config-file") {
if strings.Contains(arg, "=") {
split := strings.SplitN(arg, "=", 2)
configFile = split[1]
} else {
if i+1 >= len(spec.Process.Args) {
return configFile, ErrMalformedConfigFileParam
}
configFile = spec.Process.Args[i+1]
}
}
}
if configFile == "" {
// TODO - any more diagnostics to offer?
return configFile, ErrEngineConfigLookupFailure
}
return configFile, nil
}
var (
engineWaitInterval = 500 * time.Millisecond
engineWaitTimeout = 60 * time.Second
)
// waitForEngine will wait for the engine to start
func (c baseClient) waitForEngine(ctx context.Context, out io.Writer, healthfn func(context.Context) error) error {
ticker := time.NewTicker(engineWaitInterval)
defer ticker.Stop()
defer func() {
fmt.Fprintf(out, "\n")
}()
err := c.waitForEngineContainer(ctx, ticker)
if err != nil {
return err
}
fmt.Fprintf(out, "waiting for engine to be responsive... ")
for {
select {
case <-ticker.C:
err = healthfn(ctx)
if err == nil {
fmt.Fprintf(out, "engine is online.")
return nil
}
case <-ctx.Done():
return errors.Wrap(err, "timeout waiting for engine to be responsive")
}
}
}
func (c baseClient) waitForEngineContainer(ctx context.Context, ticker *time.Ticker) error {
var ret error
for {
select {
case <-ticker.C:
engine, err := c.GetEngine(ctx)
if engine != nil {
return nil
}
ret = err
case <-ctx.Done():
return errors.Wrap(ret, "timeout waiting for engine to be responsive")
}
}
}
// RemoveEngine gracefully unwinds the current engine
func (c baseClient) RemoveEngine(ctx context.Context, engine containerd.Container) error {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
// Make sure the container isn't being restarted while we unwind it
stopLabel := map[string]string{}
stopLabel[restart.StatusLabel] = string(containerd.Stopped)
engine.SetLabels(ctx, stopLabel)
// Wind down the existing engine
task, err := engine.Task(ctx, nil)
if err != nil {
if !errdefs.IsNotFound(err) {
return err
}
} else {
status, err := task.Status(ctx)
if err != nil {
return err
}
if status.Status == containerd.Running {
// It's running, so kill it
err := task.Kill(ctx, syscall.SIGTERM, []containerd.KillOpts{}...)
if err != nil {
return errors.Wrap(err, "task kill error")
}
ch, err := task.Wait(ctx)
if err != nil {
return err
}
timeout := time.NewTimer(engineWaitTimeout)
select {
case <-timeout.C:
// TODO - consider a force flag in the future to allow a more aggressive
// kill of the engine via
// task.Kill(ctx, syscall.SIGKILL, containerd.WithKillAll)
return ErrEngineShutdownTimeout
case <-ch:
}
}
if _, err := task.Delete(ctx); err != nil {
return err
}
}
deleteOpts := []containerd.DeleteOpts{containerd.WithSnapshotCleanup}
err = engine.Delete(ctx, deleteOpts...)
if err != nil && errdefs.IsNotFound(err) {
return nil
}
return errors.Wrap(err, "failed to remove existing engine container")
}
// startEngineOnContainerd creates a new docker engine running on containerd
func (c baseClient) startEngineOnContainerd(ctx context.Context, imageName, configFile string) error {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
image, err := c.cclient.GetImage(ctx, imageName)
if err != nil {
if errdefs.IsNotFound(err) {
return fmt.Errorf("engine image missing: %s", imageName)
}
return errors.Wrap(err, "failed to check for engine image")
}
// Make sure we have a valid config file
err = c.verifyDockerConfig(configFile)
if err != nil {
return err
}
engineSpec.Process.Args = append(engineSpec.Process.Args,
"--config-file", configFile,
)
cOpts := []containerd.NewContainerOpts{
containerized.WithNewSnapshot(image),
restart.WithStatus(containerd.Running),
restart.WithLogPath("/var/log/engine.log"), // TODO - better!
genSpec(),
containerd.WithRuntime("io.containerd.runtime.process.v1", nil),
}
_, err = c.cclient.NewContainer(
ctx,
engineContainerName,
cOpts...,
)
if err != nil {
return errors.Wrap(err, "failed to create engine container")
}
return nil
}
-537
View File
@@ -1,537 +0,0 @@
package containerizedengine
import (
"context"
"fmt"
"syscall"
"testing"
"time"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cio"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/oci"
"github.com/docker/docker/api/types"
"github.com/opencontainers/runtime-spec/specs-go"
"gotest.tools/assert"
)
func healthfnHappy(ctx context.Context) error {
return nil
}
func healthfnError(ctx context.Context) error {
return fmt.Errorf("ping failure")
}
func TestInitGetEngineFail(t *testing.T) {
ctx := context.Background()
opts := EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: CommunityEngineImage,
}
container := &fakeContainer{}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
err := client.InitEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
assert.Assert(t, err == ErrEngineAlreadyPresent)
}
func TestInitCheckImageFail(t *testing.T) {
ctx := context.Background()
opts := EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: CommunityEngineImage,
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, fmt.Errorf("something went wrong")
},
},
}
err := client.InitEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "unable to check for image")
assert.ErrorContains(t, err, "something went wrong")
}
func TestInitPullFail(t *testing.T) {
ctx := context.Background()
opts := EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: CommunityEngineImage,
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, errdefs.ErrNotFound
},
pullFunc: func(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error) {
return nil, fmt.Errorf("pull failure")
},
},
}
err := client.InitEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "unable to pull image")
assert.ErrorContains(t, err, "pull failure")
}
func TestInitStartFail(t *testing.T) {
ctx := context.Background()
opts := EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: CommunityEngineImage,
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, errdefs.ErrNotFound
},
pullFunc: func(ctx context.Context, ref string, opts ...containerd.RemoteOpt) (containerd.Image, error) {
return nil, nil
},
},
}
err := client.InitEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "failed to create docker daemon")
}
func TestGetEngineFail(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return nil, fmt.Errorf("container failure")
},
},
}
_, err := client.GetEngine(ctx)
assert.ErrorContains(t, err, "failure")
}
func TestGetEngineNotPresent(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
_, err := client.GetEngine(ctx)
assert.Assert(t, err == ErrEngineNotPresent)
}
func TestGetEngineFound(t *testing.T) {
ctx := context.Background()
container := &fakeContainer{}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
c, err := client.GetEngine(ctx)
assert.NilError(t, err)
assert.Equal(t, c, container)
}
func TestGetEngineImageFail(t *testing.T) {
client := baseClient{}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return nil, fmt.Errorf("failure")
},
}
_, err := client.getEngineImage(container)
assert.ErrorContains(t, err, "failure")
}
func TestGetEngineImagePass(t *testing.T) {
client := baseClient{}
image := &fakeImage{
nameFunc: func() string {
return "imagenamehere"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
name, err := client.getEngineImage(container)
assert.NilError(t, err)
assert.Equal(t, name, "imagenamehere")
}
func TestWaitForEngineNeverShowsUp(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
engineWaitInterval = 1 * time.Millisecond
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
err := client.waitForEngine(ctx, &testOutStream{}, healthfnError)
assert.ErrorContains(t, err, "timeout waiting")
}
func TestWaitForEnginePingFail(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
engineWaitInterval = 1 * time.Millisecond
container := &fakeContainer{}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
err := client.waitForEngine(ctx, &testOutStream{}, healthfnError)
assert.ErrorContains(t, err, "ping fail")
}
func TestWaitForEngineHealthy(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
engineWaitInterval = 1 * time.Millisecond
container := &fakeContainer{}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
err := client.waitForEngine(ctx, &testOutStream{}, healthfnHappy)
assert.NilError(t, err)
}
func TestRemoveEngineBadTaskBadDelete(t *testing.T) {
ctx := context.Background()
client := baseClient{}
container := &fakeContainer{
deleteFunc: func(context.Context, ...containerd.DeleteOpts) error {
return fmt.Errorf("delete failure")
},
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return nil, errdefs.ErrNotFound
},
}
err := client.RemoveEngine(ctx, container)
assert.ErrorContains(t, err, "failed to remove existing engine")
assert.ErrorContains(t, err, "delete failure")
}
func TestRemoveEngineTaskNoStatus(t *testing.T) {
ctx := context.Background()
client := baseClient{}
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{}, fmt.Errorf("task status failure")
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.ErrorContains(t, err, "task status failure")
}
func TestRemoveEngineTaskNotRunningDeleteFail(t *testing.T) {
ctx := context.Background()
client := baseClient{}
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Unknown}, nil
},
deleteFunc: func(context.Context, ...containerd.ProcessDeleteOpts) (*containerd.ExitStatus, error) {
return nil, fmt.Errorf("task delete failure")
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.ErrorContains(t, err, "task delete failure")
}
func TestRemoveEngineTaskRunningKillFail(t *testing.T) {
ctx := context.Background()
client := baseClient{}
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Running}, nil
},
killFunc: func(context.Context, syscall.Signal, ...containerd.KillOpts) error {
return fmt.Errorf("task kill failure")
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.ErrorContains(t, err, "task kill failure")
}
func TestRemoveEngineTaskRunningWaitFail(t *testing.T) {
ctx := context.Background()
client := baseClient{}
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Running}, nil
},
waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) {
return nil, fmt.Errorf("task wait failure")
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.ErrorContains(t, err, "task wait failure")
}
func TestRemoveEngineTaskRunningHappyPath(t *testing.T) {
ctx := context.Background()
client := baseClient{}
ch := make(chan containerd.ExitStatus, 1)
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Running}, nil
},
waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) {
ch <- containerd.ExitStatus{}
return ch, nil
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.NilError(t, err)
}
func TestRemoveEngineTaskKillTimeout(t *testing.T) {
ctx := context.Background()
ch := make(chan containerd.ExitStatus, 1)
client := baseClient{}
engineWaitTimeout = 10 * time.Millisecond
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Running}, nil
},
waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) {
//ch <- containerd.ExitStatus{} // let it timeout
return ch, nil
},
}
container := &fakeContainer{
taskFunc: func(context.Context, cio.Attach) (containerd.Task, error) {
return task, nil
},
}
err := client.RemoveEngine(ctx, container)
assert.Assert(t, err == ErrEngineShutdownTimeout)
}
func TestStartEngineOnContainerdImageErr(t *testing.T) {
ctx := context.Background()
imageName := "testnamegoeshere"
configFile := "/tmp/configfilegoeshere"
client := baseClient{
cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, fmt.Errorf("some image lookup failure")
},
},
}
err := client.startEngineOnContainerd(ctx, imageName, configFile)
assert.ErrorContains(t, err, "some image lookup failure")
}
func TestStartEngineOnContainerdImageNotFound(t *testing.T) {
ctx := context.Background()
imageName := "testnamegoeshere"
configFile := "/tmp/configfilegoeshere"
client := baseClient{
cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, errdefs.ErrNotFound
},
},
}
err := client.startEngineOnContainerd(ctx, imageName, configFile)
assert.ErrorContains(t, err, "engine image missing")
}
func TestStartEngineOnContainerdHappy(t *testing.T) {
ctx := context.Background()
imageName := "testnamegoeshere"
configFile := "/tmp/configfilegoeshere"
ch := make(chan containerd.ExitStatus, 1)
streams := cio.Streams{}
task := &fakeTask{
statusFunc: func(context.Context) (containerd.Status, error) {
return containerd.Status{Status: containerd.Running}, nil
},
waitFunc: func(context.Context) (<-chan containerd.ExitStatus, error) {
ch <- containerd.ExitStatus{}
return ch, nil
},
}
container := &fakeContainer{
newTaskFunc: func(ctx context.Context, creator cio.Creator, opts ...containerd.NewTaskOpts) (containerd.Task, error) {
if streams.Stdout != nil {
streams.Stdout.Write([]byte("{}"))
}
return task, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return nil, nil
},
newContainerFunc: func(ctx context.Context, id string, opts ...containerd.NewContainerOpts) (containerd.Container, error) {
return container, nil
},
},
}
err := client.startEngineOnContainerd(ctx, imageName, configFile)
assert.NilError(t, err)
}
func TestGetEngineConfigFilePathBadSpec(t *testing.T) {
ctx := context.Background()
client := baseClient{}
container := &fakeContainer{
specFunc: func(context.Context) (*oci.Spec, error) {
return nil, fmt.Errorf("spec error")
},
}
_, err := client.getEngineConfigFilePath(ctx, container)
assert.ErrorContains(t, err, "spec error")
}
func TestGetEngineConfigFilePathDistinct(t *testing.T) {
ctx := context.Background()
client := baseClient{}
container := &fakeContainer{
specFunc: func(context.Context) (*oci.Spec, error) {
return &oci.Spec{
Process: &specs.Process{
Args: []string{
"--another-flag",
"foo",
"--config-file",
"configpath",
},
},
}, nil
},
}
configFile, err := client.getEngineConfigFilePath(ctx, container)
assert.NilError(t, err)
assert.Assert(t, err, configFile == "configpath")
}
func TestGetEngineConfigFilePathEquals(t *testing.T) {
ctx := context.Background()
client := baseClient{}
container := &fakeContainer{
specFunc: func(context.Context) (*oci.Spec, error) {
return &oci.Spec{
Process: &specs.Process{
Args: []string{
"--another-flag=foo",
"--config-file=configpath",
},
},
}, nil
},
}
configFile, err := client.getEngineConfigFilePath(ctx, container)
assert.NilError(t, err)
assert.Assert(t, err, configFile == "configpath")
}
func TestGetEngineConfigFilePathMalformed1(t *testing.T) {
ctx := context.Background()
client := baseClient{}
container := &fakeContainer{
specFunc: func(context.Context) (*oci.Spec, error) {
return &oci.Spec{
Process: &specs.Process{
Args: []string{
"--another-flag",
"--config-file",
},
},
}, nil
},
}
_, err := client.getEngineConfigFilePath(ctx, container)
assert.Assert(t, err == ErrMalformedConfigFileParam)
}
@@ -1,16 +0,0 @@
// +build !windows
package containerizedengine
import (
"github.com/containerd/containerd"
"github.com/containerd/containerd/oci"
"github.com/docker/cli/internal/pkg/containerized"
)
func genSpec() containerd.NewContainerOpts {
return containerd.WithSpec(&engineSpec,
containerized.WithAllCapabilities,
oci.WithParentCgroupDevices,
)
}
@@ -1,14 +0,0 @@
// +build windows
package containerizedengine
import (
"github.com/containerd/containerd"
"github.com/docker/cli/internal/pkg/containerized"
)
func genSpec() containerd.NewContainerOpts {
return containerd.WithSpec(&engineSpec,
containerized.WithAllCapabilities,
)
}
-35
View File
@@ -1,35 +0,0 @@
package containerizedengine
import (
"os"
"path"
)
func (c baseClient) verifyDockerConfig(configFile string) error {
// TODO - in the future consider leveraging containerd and a host runtime
// to create the file. For now, just create it locally since we have to be
// local to talk to containerd
configDir := path.Dir(configFile)
err := os.MkdirAll(configDir, 0644)
if err != nil {
return err
}
fd, err := os.OpenFile(configFile, os.O_RDWR|os.O_CREATE, 0755)
if err != nil {
return err
}
defer fd.Close()
info, err := fd.Stat()
if err != nil {
return err
}
if info.Size() == 0 {
_, err := fd.Write([]byte("{}"))
return err
}
return nil
}
+3 -3
View File
@@ -64,7 +64,7 @@ outer:
} }
} }
err := updateNonActive(ctx, ongoing, cs, statuses, keys, activeSeen, &done, start) err := updateNonActive(ctx, ongoing, cs, statuses, &keys, activeSeen, &done, start)
if err != nil { if err != nil {
continue outer continue outer
} }
@@ -92,11 +92,11 @@ outer:
} }
} }
func updateNonActive(ctx context.Context, ongoing *jobs, cs content.Store, statuses map[string]statusInfo, keys []string, activeSeen map[string]struct{}, done *bool, start time.Time) error { func updateNonActive(ctx context.Context, ongoing *jobs, cs content.Store, statuses map[string]statusInfo, keys *[]string, activeSeen map[string]struct{}, done *bool, start time.Time) error {
for _, j := range ongoing.jobs() { for _, j := range ongoing.jobs() {
key := remotes.MakeRefKey(ctx, j) key := remotes.MakeRefKey(ctx, j)
keys = append(keys, key) *keys = append(*keys, key)
if _, ok := activeSeen[key]; ok { if _, ok := activeSeen[key]; ok {
continue continue
} }
@@ -1,12 +0,0 @@
// +build !windows
package containerizedengine
import (
"golang.org/x/sys/unix"
)
var (
// SIGKILL maps to unix.SIGKILL
SIGKILL = unix.SIGKILL
)
@@ -1,12 +0,0 @@
// +build windows
package containerizedengine
import (
"syscall"
)
var (
// SIGKILL all signals are ignored by containerd kill windows
SIGKILL = syscall.Signal(0)
)
+4 -114
View File
@@ -3,30 +3,15 @@ package containerizedengine
import ( import (
"context" "context"
"errors" "errors"
"io"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/containers" "github.com/containerd/containerd/containers"
"github.com/containerd/containerd/content" "github.com/containerd/containerd/content"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version"
specs "github.com/opencontainers/runtime-spec/specs-go"
) )
const ( const (
// CommunityEngineImage is the repo name for the community engine containerdSockPath = "/run/containerd/containerd.sock"
CommunityEngineImage = "engine-community" engineNamespace = "com.docker"
// EnterpriseEngineImage is the repo name for the enterprise engine
EnterpriseEngineImage = "engine-enterprise"
containerdSockPath = "/run/containerd/containerd.sock"
engineContainerName = "dockerd"
engineNamespace = "docker"
// Used to signal the containerd-proxy if it should manage
proxyLabel = "com.docker/containerd-proxy.scope"
) )
var ( var (
@@ -44,80 +29,12 @@ var (
// ErrEngineShutdownTimeout returned if the engine failed to shutdown in time // ErrEngineShutdownTimeout returned if the engine failed to shutdown in time
ErrEngineShutdownTimeout = errors.New("timeout waiting for engine to exit") ErrEngineShutdownTimeout = errors.New("timeout waiting for engine to exit")
// ErrEngineImageMissingTag returned if the engine image is missing the version tag
ErrEngineImageMissingTag = errors.New("malformed engine image missing tag")
engineSpec = specs.Spec{
Root: &specs.Root{
Path: "rootfs",
},
Process: &specs.Process{
Cwd: "/",
Args: []string{
// In general, configuration should be driven by the config file, not these flags
// TODO - consider moving more of these to the config file, and make sure the defaults are set if not present.
"/sbin/dockerd",
"-s",
"overlay2",
"--containerd",
"/run/containerd/containerd.sock",
"--default-runtime",
"containerd",
"--add-runtime",
"containerd=runc",
},
User: specs.User{
UID: 0,
GID: 0,
},
Env: []string{
"PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin",
},
NoNewPrivileges: false,
},
}
) )
// Client can be used to manage the lifecycle of
// dockerd running as a container on containerd.
type Client interface {
Close() error
ActivateEngine(ctx context.Context,
opts EngineInitOptions,
out OutStream,
authConfig *types.AuthConfig,
healthfn func(context.Context) error) error
InitEngine(ctx context.Context,
opts EngineInitOptions,
out OutStream,
authConfig *types.AuthConfig,
healthfn func(context.Context) error) error
DoUpdate(ctx context.Context,
opts EngineInitOptions,
out OutStream,
authConfig *types.AuthConfig,
healthfn func(context.Context) error) error
GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (AvailableVersions, error)
GetEngine(ctx context.Context) (containerd.Container, error)
RemoveEngine(ctx context.Context, engine containerd.Container) error
GetCurrentEngineVersion(ctx context.Context) (EngineInitOptions, error)
}
type baseClient struct { type baseClient struct {
cclient containerdClient cclient containerdClient
} }
// EngineInitOptions contains the configuration settings
// use during initialization of a containerized docker engine
type EngineInitOptions struct {
RegistryPrefix string
EngineImage string
EngineVersion string
ConfigFile string
scope string
}
// containerdClient abstracts the containerd client to aid in testability // containerdClient abstracts the containerd client to aid in testability
type containerdClient interface { type containerdClient interface {
Containers(ctx context.Context, filters ...string) ([]containerd.Container, error) Containers(ctx context.Context, filters ...string) ([]containerd.Container, error)
@@ -127,33 +44,6 @@ type containerdClient interface {
Close() error Close() error
ContentStore() content.Store ContentStore() content.Store
ContainerService() containers.Store ContainerService() containers.Store
} Install(context.Context, containerd.Image, ...containerd.InstallOpts) error
Version(ctx context.Context) (containerd.Version, error)
// AvailableVersions groups the available versions which were discovered
type AvailableVersions struct {
Downgrades []DockerVersion
Patches []DockerVersion
Upgrades []DockerVersion
}
// DockerVersion wraps a semantic version to retain the original tag
// since the docker date based versions don't strictly follow semantic
// versioning (leading zeros, etc.)
type DockerVersion struct {
ver.Version
Tag string
}
// Update stores available updates for rendering in a table
type Update struct {
Type string
Version string
Notes string
}
// OutStream is an output stream used to write normal program output.
type OutStream interface {
io.Writer
FD() uintptr
IsTerminal() bool
} }
+125 -72
View File
@@ -2,79 +2,54 @@ package containerizedengine
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"path"
"strings" "strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/docker/cli/internal/pkg/containerized" "github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
ver "github.com/hashicorp/go-version"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// GetCurrentEngineVersion determines the current type of engine (image) and version
func (c baseClient) GetCurrentEngineVersion(ctx context.Context) (EngineInitOptions, error) {
ctx = namespaces.WithNamespace(ctx, engineNamespace)
ret := EngineInitOptions{}
currentEngine := CommunityEngineImage
engine, err := c.GetEngine(ctx)
if err != nil {
if err == ErrEngineNotPresent {
return ret, errors.Wrap(err, "failed to find existing engine")
}
return ret, err
}
imageName, err := c.getEngineImage(engine)
if err != nil {
return ret, err
}
distributionRef, err := reference.ParseNormalizedNamed(imageName)
if err != nil {
return ret, errors.Wrapf(err, "failed to parse image name: %s", imageName)
}
if strings.Contains(distributionRef.Name(), EnterpriseEngineImage) {
currentEngine = EnterpriseEngineImage
}
taggedRef, ok := distributionRef.(reference.NamedTagged)
if !ok {
return ret, ErrEngineImageMissingTag
}
ret.EngineImage = currentEngine
ret.EngineVersion = taggedRef.Tag()
ret.RegistryPrefix = reference.Domain(taggedRef) + "/" + path.Dir(reference.Path(taggedRef))
return ret, nil
}
// ActivateEngine will switch the image from the CE to EE image // ActivateEngine will switch the image from the CE to EE image
func (c baseClient) ActivateEngine(ctx context.Context, opts EngineInitOptions, out OutStream, func (c *baseClient) ActivateEngine(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
authConfig *types.AuthConfig, healthfn func(context.Context) error) error { authConfig *types.AuthConfig) error {
// set the proxy scope to "ee" for activate flows // If the user didn't specify an image, determine the correct enterprise image to use
opts.scope = "ee" if opts.EngineImage == "" {
localMetadata, err := versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
ctx = namespaces.WithNamespace(ctx, engineNamespace)
// If version is unspecified, use the existing engine version
if opts.EngineVersion == "" {
currentOpts, err := c.GetCurrentEngineVersion(ctx)
if err != nil { if err != nil {
return err return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image")
} }
opts.EngineVersion = currentOpts.EngineVersion
if currentOpts.EngineImage == EnterpriseEngineImage { engineImage := localMetadata.EngineImage
// This is a "no-op" activation so the only change would be the license - don't update the engine itself if engineImage == clitypes.EnterpriseEngineImage || engineImage == clitypes.CommunityEngineImage {
return nil opts.EngineImage = clitypes.EnterpriseEngineImage
} else {
// Chop off the standard prefix and retain any trailing OS specific image details
// e.g., engine-community-dm -> engine-enterprise-dm
engineImage = strings.TrimPrefix(engineImage, clitypes.EnterpriseEngineImage)
engineImage = strings.TrimPrefix(engineImage, clitypes.CommunityEngineImage)
opts.EngineImage = clitypes.EnterpriseEngineImage + engineImage
} }
} }
return c.DoUpdate(ctx, opts, out, authConfig, healthfn)
ctx = namespaces.WithNamespace(ctx, engineNamespace)
return c.DoUpdate(ctx, opts, out, authConfig)
} }
// DoUpdate performs the underlying engine update // DoUpdate performs the underlying engine update
func (c baseClient) DoUpdate(ctx context.Context, opts EngineInitOptions, out OutStream, func (c *baseClient) DoUpdate(ctx context.Context, opts clitypes.EngineInitOptions, out clitypes.OutStream,
authConfig *types.AuthConfig, healthfn func(context.Context) error) error { authConfig *types.AuthConfig) error {
ctx = namespaces.WithNamespace(ctx, engineNamespace) ctx = namespaces.WithNamespace(ctx, engineNamespace)
if opts.EngineVersion == "" { if opts.EngineVersion == "" {
@@ -83,7 +58,16 @@ func (c baseClient) DoUpdate(ctx context.Context, opts EngineInitOptions, out Ou
// current engine version and automatically apply it so users // current engine version and automatically apply it so users
// could stay in sync by simply having a scheduled // could stay in sync by simply having a scheduled
// `docker engine update` // `docker engine update`
return fmt.Errorf("please pick the version you want to update to") return fmt.Errorf("pick the version you want to update to with --version")
}
var localMetadata *clitypes.RuntimeMetadata
if opts.EngineImage == "" {
var err error
localMetadata, err = versions.GetCurrentRuntimeMetadata(opts.RuntimeMetadataDir)
if err != nil {
return errors.Wrap(err, "unable to determine the installed engine version. Specify which engine image to update with --engine-image set to 'engine-community' or 'engine-enterprise'")
}
opts.EngineImage = localMetadata.EngineImage
} }
imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion) imageName := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
@@ -101,30 +85,99 @@ func (c baseClient) DoUpdate(ctx context.Context, opts EngineInitOptions, out Ou
} }
} }
// Gather information about the existing engine so we can recreate it // Make sure we're safe to proceed
engine, err := c.GetEngine(ctx) newMetadata, err := c.PreflightCheck(ctx, image)
if err != nil { if err != nil {
if err == ErrEngineNotPresent { return err
return errors.Wrap(err, "unable to find existing engine - please use init") }
if localMetadata != nil {
if localMetadata.Platform != newMetadata.Platform {
fmt.Fprintf(out, "\nNotice: you have switched to \"%s\". Refer to %s for update instructions.\n\n", newMetadata.Platform, getReleaseNotesURL(imageName))
} }
}
if err := c.cclient.Install(ctx, image, containerd.WithInstallReplace, containerd.WithInstallPath("/usr")); err != nil {
return err return err
} }
// TODO verify the image has changed and don't update if nothing has changed return versions.WriteRuntimeMetadata(opts.RuntimeMetadataDir, newMetadata)
}
err = containerized.AtomicImageUpdate(ctx, engine, image, func() error { // PreflightCheck verifies the specified image is compatible with the local system before proceeding to update/activate
ctx, cancel := context.WithTimeout(ctx, engineWaitTimeout) // If things look good, the RuntimeMetadata for the new image is returned and can be written out to the host
defer cancel() func (c *baseClient) PreflightCheck(ctx context.Context, image containerd.Image) (*clitypes.RuntimeMetadata, error) {
return c.waitForEngine(ctx, out, healthfn) var metadata clitypes.RuntimeMetadata
}) ic, err := image.Config(ctx)
if err == nil && opts.scope != "" { if err != nil {
var labels map[string]string return nil, err
labels, err = engine.Labels(ctx) }
var (
ociimage v1.Image
config v1.ImageConfig
)
switch ic.MediaType {
case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config:
p, err := content.ReadBlob(ctx, image.ContentStore(), ic)
if err != nil { if err != nil {
return err return nil, err
}
if err := json.Unmarshal(p, &ociimage); err != nil {
return nil, err
}
config = ociimage.Config
default:
return nil, fmt.Errorf("unknown image %s config media type %s", image.Name(), ic.MediaType)
}
metadataString, ok := config.Labels["com.docker."+clitypes.RuntimeMetadataName]
if !ok {
return nil, fmt.Errorf("image %s does not contain runtime metadata label %s", image.Name(), clitypes.RuntimeMetadataName)
}
err = json.Unmarshal([]byte(metadataString), &metadata)
if err != nil {
return nil, errors.Wrapf(err, "malformed runtime metadata file in %s", image.Name())
}
// Current CLI only supports host install runtime
if metadata.Runtime != "host_install" {
return nil, fmt.Errorf("unsupported daemon image: %s\nConsult the release notes at %s for upgrade instructions", metadata.Runtime, getReleaseNotesURL(image.Name()))
}
// Verify local containerd is new enough
localVersion, err := c.cclient.Version(ctx)
if err != nil {
return nil, err
}
if metadata.ContainerdMinVersion != "" {
lv, err := ver.NewVersion(localVersion.Version)
if err != nil {
return nil, err
}
mv, err := ver.NewVersion(metadata.ContainerdMinVersion)
if err != nil {
return nil, err
}
if lv.LessThan(mv) {
return nil, fmt.Errorf("local containerd is too old: %s - this engine version requires %s or newer.\nConsult the release notes at %s for upgrade instructions",
localVersion.Version, metadata.ContainerdMinVersion, getReleaseNotesURL(image.Name()))
}
} // If omitted on metadata, no hard dependency on containerd version beyond 18.09 baseline
// All checks look OK, proceed with update
return &metadata, nil
}
// getReleaseNotesURL returns a release notes url
// If the image name does not contain a version tag, the base release notes URL is returned
func getReleaseNotesURL(imageName string) string {
versionTag := ""
distributionRef, err := reference.ParseNormalizedNamed(imageName)
if err == nil {
taggedRef, ok := distributionRef.(reference.NamedTagged)
if ok {
versionTag = taggedRef.Tag()
} }
labels[proxyLabel] = opts.scope
_, err = engine.SetLabels(ctx, labels)
} }
return err return fmt.Sprintf("%s?%s", clitypes.ReleaseNotePrefix, versionTag)
} }
+167 -185
View File
@@ -1,169 +1,78 @@
package containerizedengine package containerizedengine
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"io/ioutil"
"os"
"testing" "testing"
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/cio" "github.com/containerd/containerd/cio"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/versions"
clitypes "github.com/docker/cli/types"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/assert" "gotest.tools/assert"
) )
func TestGetCurrentEngineVersionHappy(t *testing.T) { func TestActivateImagePermutations(t *testing.T) {
ctx := context.Background() ctx := context.Background()
image := &fakeImage{ lookedup := "not called yet"
nameFunc: func() string { expectedError := fmt.Errorf("expected error")
return "acme.com/dockermirror/" + CommunityEngineImage + ":engineversion"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return []containerd.Container{container}, nil lookedup = ref
},
},
}
opts, err := client.GetCurrentEngineVersion(ctx)
assert.NilError(t, err)
assert.Equal(t, opts.EngineImage, CommunityEngineImage)
assert.Equal(t, opts.RegistryPrefix, "acme.com/dockermirror")
assert.Equal(t, opts.EngineVersion, "engineversion")
}
func TestGetCurrentEngineVersionEnterpriseHappy(t *testing.T) {
ctx := context.Background()
image := &fakeImage{
nameFunc: func() string {
return "docker.io/docker/" + EnterpriseEngineImage + ":engineversion"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
opts, err := client.GetCurrentEngineVersion(ctx)
assert.NilError(t, err)
assert.Equal(t, opts.EngineImage, EnterpriseEngineImage)
assert.Equal(t, opts.EngineVersion, "engineversion")
assert.Equal(t, opts.RegistryPrefix, "docker.io/docker")
}
func TestGetCurrentEngineVersionNoEngine(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.ErrorContains(t, err, "failed to find existing engine")
}
func TestGetCurrentEngineVersionMiscEngineError(t *testing.T) {
ctx := context.Background()
expectedError := fmt.Errorf("some container lookup error")
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return nil, expectedError return nil, expectedError
}, },
}, },
} }
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
_, err := client.GetCurrentEngineVersion(ctx) opts := clitypes.EngineInitOptions{
assert.Assert(t, err == expectedError) EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
RuntimeMetadataDir: tmpdir,
}
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))
metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage, opts.EngineVersion))
metadata = clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage + "-dm"}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, expectedError.Error())
assert.Equal(t, lookedup, fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, clitypes.EnterpriseEngineImage+"-dm", opts.EngineVersion))
} }
func TestGetCurrentEngineVersionImageFailure(t *testing.T) { func TestActivateConfigFailure(t *testing.T) {
ctx := context.Background()
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return nil, fmt.Errorf("container image failure")
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.ErrorContains(t, err, "container image failure")
}
func TestGetCurrentEngineVersionMalformed(t *testing.T) {
ctx := context.Background()
image := &fakeImage{
nameFunc: func() string {
return "imagename"
},
}
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil
},
},
}
_, err := client.GetCurrentEngineVersion(ctx)
assert.Assert(t, err == ErrEngineImageMissingTag)
}
func TestActivateNoEngine(t *testing.T) {
ctx := context.Background()
client := baseClient{
cclient: &fakeContainerdClient{
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil
},
},
}
opts := EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: EnterpriseEngineImage,
}
err := client.ActivateEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
assert.ErrorContains(t, err, "unable to find")
}
func TestActivateNoChange(t *testing.T) {
ctx := context.Background() ctx := context.Background()
registryPrefix := "registryprefixgoeshere" registryPrefix := "registryprefixgoeshere"
image := &fakeImage{ image := &fakeImage{
nameFunc: func() string { nameFunc: func() string {
return registryPrefix + "/" + EnterpriseEngineImage + ":engineversion" return registryPrefix + "/" + clitypes.EnterpriseEngineImage + ":engineversion"
},
configFunc: func(ctx context.Context) (ocispec.Descriptor, error) {
return ocispec.Descriptor{}, fmt.Errorf("config lookup failure")
}, },
} }
container := &fakeContainer{ container := &fakeContainer{
@@ -182,17 +91,27 @@ func TestActivateNoChange(t *testing.T) {
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{container}, nil return []containerd.Container{container}, nil
}, },
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return image, nil
},
}, },
} }
opts := EngineInitOptions{ tmpdir, err := ioutil.TempDir("", "engindir")
EngineVersion: "engineversiongoeshere", assert.NilError(t, err)
RegistryPrefix: "registryprefixgoeshere", defer os.RemoveAll(tmpdir)
ConfigFile: "/tmp/configfilegoeshere", metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
EngineImage: EnterpriseEngineImage, err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
err := client.ActivateEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy) err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.NilError(t, err) assert.ErrorContains(t, err, "config lookup failure")
} }
func TestActivateDoUpdateFail(t *testing.T) { func TestActivateDoUpdateFail(t *testing.T) {
@@ -219,38 +138,60 @@ func TestActivateDoUpdateFail(t *testing.T) {
}, },
}, },
} }
opts := EngineInitOptions{ tmpdir, err := ioutil.TempDir("", "enginedir")
EngineVersion: "engineversiongoeshere", assert.NilError(t, err)
RegistryPrefix: "registryprefixgoeshere", defer os.RemoveAll(tmpdir)
ConfigFile: "/tmp/configfilegoeshere", metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.CommunityEngineImage}
EngineImage: EnterpriseEngineImage, err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
err := client.ActivateEngine(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy) err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "check for image") assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong") assert.ErrorContains(t, err, "something went wrong")
} }
func TestDoUpdateNoVersion(t *testing.T) { func TestDoUpdateNoVersion(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "enginedir")
assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
ctx := context.Background() ctx := context.Background()
opts := EngineInitOptions{ opts := clitypes.EngineInitOptions{
EngineVersion: "", EngineVersion: "",
RegistryPrefix: "registryprefixgoeshere", RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere", ConfigFile: "/tmp/configfilegoeshere",
EngineImage: EnterpriseEngineImage, EngineImage: clitypes.EnterpriseEngineImage,
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{} client := baseClient{}
err := client.DoUpdate(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy) err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "please pick the version you") assert.ErrorContains(t, err, "pick the version you")
} }
func TestDoUpdateImageMiscError(t *testing.T) { func TestDoUpdateImageMiscError(t *testing.T) {
ctx := context.Background() ctx := context.Background()
opts := EngineInitOptions{ tmpdir, err := ioutil.TempDir("", "enginedir")
EngineVersion: "engineversiongoeshere", assert.NilError(t, err)
RegistryPrefix: "registryprefixgoeshere", defer os.RemoveAll(tmpdir)
ConfigFile: "/tmp/configfilegoeshere", metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
EngineImage: "testnamegoeshere", err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere",
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
@@ -260,18 +201,26 @@ func TestDoUpdateImageMiscError(t *testing.T) {
}, },
}, },
} }
err := client.DoUpdate(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "check for image") assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong") assert.ErrorContains(t, err, "something went wrong")
} }
func TestDoUpdatePullFail(t *testing.T) { func TestDoUpdatePullFail(t *testing.T) {
ctx := context.Background() ctx := context.Background()
opts := EngineInitOptions{ tmpdir, err := ioutil.TempDir("", "enginedir")
EngineVersion: "engineversiongoeshere", assert.NilError(t, err)
RegistryPrefix: "registryprefixgoeshere", defer os.RemoveAll(tmpdir)
ConfigFile: "/tmp/configfilegoeshere", metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
EngineImage: "testnamegoeshere", err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere",
RuntimeMetadataDir: tmpdir,
} }
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
@@ -284,35 +233,68 @@ func TestDoUpdatePullFail(t *testing.T) {
}, },
}, },
} }
err := client.DoUpdate(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy)
err = client.DoUpdate(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "unable to pull") assert.ErrorContains(t, err, "unable to pull")
assert.ErrorContains(t, err, "pull failure") assert.ErrorContains(t, err, "pull failure")
} }
func TestDoUpdateEngineMissing(t *testing.T) { func TestActivateDoUpdateVerifyImageName(t *testing.T) {
ctx := context.Background() ctx := context.Background()
opts := EngineInitOptions{ registryPrefix := "registryprefixgoeshere"
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
ConfigFile: "/tmp/configfilegoeshere",
EngineImage: "testnamegoeshere",
}
image := &fakeImage{ image := &fakeImage{
nameFunc: func() string { nameFunc: func() string {
return "imagenamehere" return registryPrefix + "/ce-engine:engineversion"
}, },
} }
container := &fakeContainer{
imageFunc: func(context.Context) (containerd.Image, error) {
return image, nil
},
}
requestedImage := "unset"
client := baseClient{ client := baseClient{
cclient: &fakeContainerdClient{ cclient: &fakeContainerdClient{
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
return image, nil
},
containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) { containersFunc: func(ctx context.Context, filters ...string) ([]containerd.Container, error) {
return []containerd.Container{}, nil return []containerd.Container{container}, nil
},
getImageFunc: func(ctx context.Context, ref string) (containerd.Image, error) {
requestedImage = ref
return nil, fmt.Errorf("something went wrong")
}, },
}, },
} }
err := client.DoUpdate(ctx, opts, &testOutStream{}, &types.AuthConfig{}, healthfnHappy) tmpdir, err := ioutil.TempDir("", "enginedir")
assert.ErrorContains(t, err, "unable to find existing engine") assert.NilError(t, err)
defer os.RemoveAll(tmpdir)
metadata := clitypes.RuntimeMetadata{EngineImage: clitypes.EnterpriseEngineImage}
err = versions.WriteRuntimeMetadata(tmpdir, &metadata)
assert.NilError(t, err)
opts := clitypes.EngineInitOptions{
EngineVersion: "engineversiongoeshere",
RegistryPrefix: "registryprefixgoeshere",
EngineImage: "testnamegoeshere",
ConfigFile: "/tmp/configfilegoeshere",
RuntimeMetadataDir: tmpdir,
}
err = client.ActivateEngine(ctx, opts, command.NewOutStream(&bytes.Buffer{}), &types.AuthConfig{})
assert.ErrorContains(t, err, "check for image")
assert.ErrorContains(t, err, "something went wrong")
expectedImage := fmt.Sprintf("%s/%s:%s", opts.RegistryPrefix, opts.EngineImage, opts.EngineVersion)
assert.Assert(t, requestedImage == expectedImage, "%s != %s", requestedImage, expectedImage)
}
func TestGetReleaseNotesURL(t *testing.T) {
imageName := "bogus image name #$%&@!"
url := getReleaseNotesURL(imageName)
assert.Equal(t, url, clitypes.ReleaseNotePrefix+"?")
imageName = "foo.bar/valid/repowithouttag"
url = getReleaseNotesURL(imageName)
assert.Equal(t, url, clitypes.ReleaseNotePrefix+"?")
imageName = "foo.bar/valid/repowithouttag:tag123"
url = getReleaseNotesURL(imageName)
assert.Equal(t, url, clitypes.ReleaseNotePrefix+"?tag123")
} }
-72
View File
@@ -1,72 +0,0 @@
package containerizedengine
import (
"context"
"sort"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/distribution/reference"
ver "github.com/hashicorp/go-version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// GetEngineVersions reports the versions of the engine that are available
func (c baseClient) GetEngineVersions(ctx context.Context, registryClient registryclient.RegistryClient, currentVersion, imageName string) (AvailableVersions, error) {
imageRef, err := reference.ParseNormalizedNamed(imageName)
if err != nil {
return AvailableVersions{}, err
}
tags, err := registryClient.GetTags(ctx, imageRef)
if err != nil {
return AvailableVersions{}, err
}
return parseTags(tags, currentVersion)
}
func parseTags(tags []string, currentVersion string) (AvailableVersions, error) {
var ret AvailableVersions
currentVer, err := ver.NewVersion(currentVersion)
if err != nil {
return ret, errors.Wrapf(err, "failed to parse existing version %s", currentVersion)
}
downgrades := []DockerVersion{}
patches := []DockerVersion{}
upgrades := []DockerVersion{}
currentSegments := currentVer.Segments()
for _, tag := range tags {
tmp, err := ver.NewVersion(tag)
if err != nil {
logrus.Debugf("Unable to parse %s: %s", tag, err)
continue
}
testVersion := DockerVersion{Version: *tmp, Tag: tag}
if testVersion.LessThan(currentVer) {
downgrades = append(downgrades, testVersion)
continue
}
testSegments := testVersion.Segments()
// lib always provides min 3 segments
if testSegments[0] == currentSegments[0] &&
testSegments[1] == currentSegments[1] {
patches = append(patches, testVersion)
} else {
upgrades = append(upgrades, testVersion)
}
}
sort.Slice(downgrades, func(i, j int) bool {
return downgrades[i].Version.LessThan(&downgrades[j].Version)
})
sort.Slice(patches, func(i, j int) bool {
return patches[i].Version.LessThan(&patches[j].Version)
})
sort.Slice(upgrades, func(i, j int) bool {
return upgrades[i].Version.LessThan(&upgrades[j].Version)
})
ret.Downgrades = downgrades
ret.Patches = patches
ret.Upgrades = upgrades
return ret, nil
}
+11 -3
View File
@@ -13,13 +13,14 @@ type (
getHubUserOrgsFunc func(ctx context.Context, authToken string) (orgs []model.Org, err error) getHubUserOrgsFunc func(ctx context.Context, authToken string) (orgs []model.Org, err error)
getHubUserByNameFunc func(ctx context.Context, username string) (user *model.User, err error) getHubUserByNameFunc func(ctx context.Context, username string) (user *model.User, err error)
verifyLicenseFunc func(ctx context.Context, license model.IssuedLicense) (res *model.CheckResponse, err error) verifyLicenseFunc func(ctx context.Context, license model.IssuedLicense) (res *model.CheckResponse, err error)
generateNewTrialSubscriptionFunc func(ctx context.Context, authToken, dockerID, email string) (subscriptionID string, err error) generateNewTrialSubscriptionFunc func(ctx context.Context, authToken, dockerID string) (subscriptionID string, err error)
listSubscriptionsFunc func(ctx context.Context, authToken, dockerID string) (response []*model.Subscription, err error) listSubscriptionsFunc func(ctx context.Context, authToken, dockerID string) (response []*model.Subscription, err error)
listSubscriptionsDetailsFunc func(ctx context.Context, authToken, dockerID string) (response []*model.SubscriptionDetail, err error) listSubscriptionsDetailsFunc func(ctx context.Context, authToken, dockerID string) (response []*model.SubscriptionDetail, err error)
downloadLicenseFromHubFunc func(ctx context.Context, authToken, subscriptionID string) (license *model.IssuedLicense, err error) downloadLicenseFromHubFunc func(ctx context.Context, authToken, subscriptionID string) (license *model.IssuedLicense, err error)
parseLicenseFunc func(license []byte) (parsedLicense *model.IssuedLicense, err error) parseLicenseFunc func(license []byte) (parsedLicense *model.IssuedLicense, err error)
storeLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error storeLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error
loadLocalLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient) (*model.Subscription, error) loadLocalLicenseFunc func(ctx context.Context, dclnt licensing.WrappedDockerClient) (*model.Subscription, error)
summarizeLicenseFunc func(*model.CheckResponse, string) *model.Subscription
} }
) )
@@ -51,9 +52,9 @@ func (c *fakeLicensingClient) VerifyLicense(ctx context.Context, license model.I
return nil, nil return nil, nil
} }
func (c *fakeLicensingClient) GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID, email string) (subscriptionID string, err error) { func (c *fakeLicensingClient) GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID string) (subscriptionID string, err error) {
if c.generateNewTrialSubscriptionFunc != nil { if c.generateNewTrialSubscriptionFunc != nil {
return c.generateNewTrialSubscriptionFunc(ctx, authToken, dockerID, email) return c.generateNewTrialSubscriptionFunc(ctx, authToken, dockerID)
} }
return "", nil return "", nil
} }
@@ -102,3 +103,10 @@ func (c *fakeLicensingClient) LoadLocalLicense(ctx context.Context, dclnt licens
} }
return nil, nil return nil, nil
} }
func (c *fakeLicensingClient) SummarizeLicense(cr *model.CheckResponse, keyid string) *model.Subscription {
if c.summarizeLicenseFunc != nil {
return c.summarizeLicenseFunc(cr, keyid)
}
return nil
}
+4 -1
View File
@@ -10,7 +10,10 @@ var (
// licensingPublicKey is the official public license key for store.docker.com // licensingPublicKey is the official public license key for store.docker.com
// nolint: lll // nolint: lll
licensingPublicKey = "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0Ka2lkOiBKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVAoKTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF5ZEl5K2xVN283UGNlWSs0K3MrQwpRNU9FZ0N5RjhDeEljUUlXdUs4NHBJaVpjaVk2NzMweUNZbndMU0tUbHcrVTZVQy9RUmVXUmlvTU5ORTVEczVUCllFWGJHRzZvbG0ycWRXYkJ3Y0NnKzJVVUgvT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTEKSXYrbXE3bHA1blEvUnhyVDBLWlJBUVRZTEVNRWZHd20zaE1PL2dlTFBTK2hnS1B0SUhsa2c2L1djb3hUR29LUAo3OWQvd2FIWXhHTmw3V2hTbmVpQlN4YnBiUUFLazIxbGc3OThYYjd2WnlFQVRETXJSUjlNZUU2QWRqNUhKcFkzCkNveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTyt3YTJLTjhVd2dJSW0waTVJMXVXOUdrcTQKempCeTV6aGdxdVVYYkc5YldQQU9ZcnE1UWE4MUR4R2NCbEp5SFlBcCtERFBFOVRHZzR6WW1YakpueFpxSEVkdQpHcWRldlo4WE1JMHVrZmtHSUkxNHdVT2lNSUlJclhsRWNCZi80Nkk4Z1FXRHp4eWNaZS9KR1grTEF1YXlYcnlyClVGZWhWTlVkWlVsOXdYTmFKQitrYUNxejVRd2FSOTNzR3crUVNmdEQwTnZMZTdDeU9IK0U2dmc2U3QvTmVUdmcKdjhZbmhDaVhJbFo4SE9mSXdOZTd0RUYvVWN6NU9iUHlrbTN0eWxyTlVqdDBWeUFtdHRhY1ZJMmlHaWhjVVBybQprNGxWSVo3VkQvTFNXK2k3eW9TdXJ0cHNQWGNlMnBLRElvMzBsSkdoTy8zS1VtbDJTVVpDcXpKMXlFbUtweXNICjVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=" licensingPublicKeys = []string{
"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0Ka2lkOiBKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVAoKTUlJQ0lqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FnOEFNSUlDQ2dLQ0FnRUF5ZEl5K2xVN283UGNlWSs0K3MrQwpRNU9FZ0N5RjhDeEljUUlXdUs4NHBJaVpjaVk2NzMweUNZbndMU0tUbHcrVTZVQy9RUmVXUmlvTU5ORTVEczVUCllFWGJHRzZvbG0ycWRXYkJ3Y0NnKzJVVUgvT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTEKSXYrbXE3bHA1blEvUnhyVDBLWlJBUVRZTEVNRWZHd20zaE1PL2dlTFBTK2hnS1B0SUhsa2c2L1djb3hUR29LUAo3OWQvd2FIWXhHTmw3V2hTbmVpQlN4YnBiUUFLazIxbGc3OThYYjd2WnlFQVRETXJSUjlNZUU2QWRqNUhKcFkzCkNveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTyt3YTJLTjhVd2dJSW0waTVJMXVXOUdrcTQKempCeTV6aGdxdVVYYkc5YldQQU9ZcnE1UWE4MUR4R2NCbEp5SFlBcCtERFBFOVRHZzR6WW1YakpueFpxSEVkdQpHcWRldlo4WE1JMHVrZmtHSUkxNHdVT2lNSUlJclhsRWNCZi80Nkk4Z1FXRHp4eWNaZS9KR1grTEF1YXlYcnlyClVGZWhWTlVkWlVsOXdYTmFKQitrYUNxejVRd2FSOTNzR3crUVNmdEQwTnZMZTdDeU9IK0U2dmc2U3QvTmVUdmcKdjhZbmhDaVhJbFo4SE9mSXdOZTd0RUYvVWN6NU9iUHlrbTN0eWxyTlVqdDBWeUFtdHRhY1ZJMmlHaWhjVVBybQprNGxWSVo3VkQvTFNXK2k3eW9TdXJ0cHNQWGNlMnBLRElvMzBsSkdoTy8zS1VtbDJTVVpDcXpKMXlFbUtweXNICjVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=",
"LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0Ka2V5SUQ6IFpGSjI6Q1c1Szo1M0tSOlo0NUg6NlpVQzpJNFhFOlpUS1A6TVQ1UjpQWFpMOlNTNE46RjQ0NDo0U1Q0CmtpZDogWkZKMjpDVzVLOjUzS1I6WjQ1SDo2WlVDOkk0WEU6WlRLUDpNVDVSOlBYWkw6U1M0TjpGNDQ0OjRTVDQKCk1JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBTUlJQkNnS0NBUUVBd1FhVVRaUFhQZnloZFZVdkJkbXkKZlViYXZYL1pmdkNkMCtGREdNb0ZQazlUTlE1aVZPSkhaUVVNa2N2d2QrdVdaV3dvdWtEUGhZaWxEQTZ6Y3krQQowdERFQkF0Nmc5TGM3UFNXU1BZMTJpbWxnbC85RmJzQnZsSjFRc1RJNGlPUjQ1K0FsMHMxMWhaNG0wR1k4UXQ4CnpFN0RYU1BNUzVRTHlUcHlEemZkQURVcWFGRVcxNTVOQ3BaKzZ6N0lHZCt0V2xjalB3QzQwb3ppbWM1bXVUSWgKb2w1WG1hUFREYk45VzhDWGQ1ZWdUeEExZU43YTA3MWR0R1RialFMUEhvb0QxRURsbitvZjZ2VGFReUphWWJmQgpNRHF2NFdraG9QSzJPWWZ5OXVLR1lTNS9ieHIzUWVTUGRoWVFrQzl2YVZsRUtuTjFZaER6VXZVZGR1c3lyRUdICjd3SURBUUFCCi0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=",
}
) )
type ( type (
+36 -23
View File
@@ -1,6 +1,7 @@
package licenseutils package licenseutils
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -19,7 +20,7 @@ import (
// HubUser wraps a licensing client and holds key information // HubUser wraps a licensing client and holds key information
// for a user to avoid multiple lookups // for a user to avoid multiple lookups
type HubUser struct { type HubUser struct {
client licensing.Client Client licensing.Client
token string token string
User model.User User model.User
Orgs []model.Org Orgs []model.Org
@@ -35,18 +36,22 @@ func (u HubUser) GetOrgByID(orgID string) (model.Org, error) {
return model.Org{}, fmt.Errorf("org %s not found", orgID) return model.Org{}, fmt.Errorf("org %s not found", orgID)
} }
// Login to the license server and return a client that can be used to look up and download license files or generate new trial licenses func getClient() (licensing.Client, error) {
func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
baseURI, err := url.Parse(licensingDefaultBaseURI) baseURI, err := url.Parse(licensingDefaultBaseURI)
if err != nil { if err != nil {
return HubUser{}, err return nil, err
} }
lclient, err := licensing.New(&licensing.Config{ return licensing.New(&licensing.Config{
BaseURI: *baseURI, BaseURI: *baseURI,
HTTPClient: &http.Client{}, HTTPClient: &http.Client{},
PublicKey: licensingPublicKey, PublicKeys: licensingPublicKeys,
}) })
}
// Login to the license server and return a client that can be used to look up and download license files or generate new trial licenses
func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
lclient, err := getClient()
if err != nil { if err != nil {
return HubUser{}, err return HubUser{}, err
} }
@@ -68,7 +73,7 @@ func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
return HubUser{}, err return HubUser{}, err
} }
return HubUser{ return HubUser{
client: lclient, Client: lclient,
token: token, token: token,
User: *user, User: *user,
Orgs: orgs, Orgs: orgs,
@@ -78,12 +83,12 @@ func Login(ctx context.Context, authConfig *types.AuthConfig) (HubUser, error) {
// GetAvailableLicenses finds all available licenses for a given account and their orgs // GetAvailableLicenses finds all available licenses for a given account and their orgs
func (u HubUser) GetAvailableLicenses(ctx context.Context) ([]LicenseDisplay, error) { func (u HubUser) GetAvailableLicenses(ctx context.Context) ([]LicenseDisplay, error) {
subs, err := u.client.ListSubscriptions(ctx, u.token, u.User.ID) subs, err := u.Client.ListSubscriptions(ctx, u.token, u.User.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, org := range u.Orgs { for _, org := range u.Orgs {
orgSub, err := u.client.ListSubscriptions(ctx, u.token, org.ID) orgSub, err := u.Client.ListSubscriptions(ctx, u.token, org.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,7 +102,7 @@ func (u HubUser) GetAvailableLicenses(ctx context.Context) ([]LicenseDisplay, er
// Filter out expired licenses // Filter out expired licenses
i := 0 i := 0
for _, s := range subs { for _, s := range subs {
if s.State != "expired" && s.Expires != nil { if s.State == "active" && s.Expires != nil {
owner := "" owner := ""
if s.DockerID == u.User.ID { if s.DockerID == u.User.ID {
owner = u.User.Username owner = u.User.Username
@@ -129,42 +134,50 @@ func (u HubUser) GetAvailableLicenses(ctx context.Context) ([]LicenseDisplay, er
// GenerateTrialLicense will generate a new trial license for the specified user or org // GenerateTrialLicense will generate a new trial license for the specified user or org
func (u HubUser) GenerateTrialLicense(ctx context.Context, targetID string) (*model.IssuedLicense, error) { func (u HubUser) GenerateTrialLicense(ctx context.Context, targetID string) (*model.IssuedLicense, error) {
subID, err := u.client.GenerateNewTrialSubscription(ctx, u.token, targetID, u.User.Email) subID, err := u.Client.GenerateNewTrialSubscription(ctx, u.token, targetID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return u.client.DownloadLicenseFromHub(ctx, u.token, subID) return u.Client.DownloadLicenseFromHub(ctx, u.token, subID)
} }
// GetIssuedLicense will download a license by ID // GetIssuedLicense will download a license by ID
func (u HubUser) GetIssuedLicense(ctx context.Context, ID string) (*model.IssuedLicense, error) { func (u HubUser) GetIssuedLicense(ctx context.Context, ID string) (*model.IssuedLicense, error) {
return u.client.DownloadLicenseFromHub(ctx, u.token, ID) return u.Client.DownloadLicenseFromHub(ctx, u.token, ID)
} }
// LoadLocalIssuedLicense will load a local license file // LoadLocalIssuedLicense will load a local license file
func LoadLocalIssuedLicense(ctx context.Context, filename string) (*model.IssuedLicense, error) { func LoadLocalIssuedLicense(ctx context.Context, filename string) (*model.IssuedLicense, error) {
baseURI, err := url.Parse(licensingDefaultBaseURI) lclient, err := getClient()
if err != nil {
return nil, err
}
lclient, err := licensing.New(&licensing.Config{
BaseURI: *baseURI,
HTTPClient: &http.Client{},
PublicKey: licensingPublicKey,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return doLoadLocalIssuedLicense(ctx, filename, lclient) return doLoadLocalIssuedLicense(ctx, filename, lclient)
} }
// GetLicenseSummary summarizes the license for the user
func GetLicenseSummary(ctx context.Context, license model.IssuedLicense) (string, error) {
lclient, err := getClient()
if err != nil {
return "", err
}
cr, err := lclient.VerifyLicense(ctx, license)
if err != nil {
return "", err
}
return lclient.SummarizeLicense(cr, license.KeyID).String(), nil
}
func doLoadLocalIssuedLicense(ctx context.Context, filename string, lclient licensing.Client) (*model.IssuedLicense, error) { func doLoadLocalIssuedLicense(ctx context.Context, filename string, lclient licensing.Client) (*model.IssuedLicense, error) {
var license model.IssuedLicense var license model.IssuedLicense
data, err := ioutil.ReadFile(filename) data, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// The file may contain a leading BOM, which will choke the
// json deserializer.
data = bytes.TrimPrefix(data, []byte("\xef\xbb\xbf"))
err = json.Unmarshal(data, &license) err = json.Unmarshal(data, &license)
if err != nil { if err != nil {

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