Compare commits
52 Commits
v17.12.0-c
...
v17.10.0-c
| Author | SHA1 | Date | |
|---|---|---|---|
| f4ffd2511c | |||
| ec6b6de1c8 | |||
| 8bac3f632b | |||
| 646a76c31b | |||
| af94197a36 | |||
| 2f11502622 | |||
| 24ee573f35 | |||
| 4d73e16a14 | |||
| 1311aaf76e | |||
| 6f56735237 | |||
| e274827392 | |||
| e7f51b183f | |||
| 792e8f9a82 | |||
| 8b13940b3b | |||
| 550052ebae | |||
| b0596beda5 | |||
| 6fc2c6b8c5 | |||
| f26232e8f6 | |||
| 018b6401f5 | |||
| 56bd88c226 | |||
| fc8971da3b | |||
| 9f566ba32b | |||
| 785d4378ad | |||
| e9a538eda6 | |||
| cdee6d83f4 | |||
| c76d10969f | |||
| 02f6ddc6ae | |||
| 125ffb8b1b | |||
| 9c61899f29 | |||
| 76922d8690 | |||
| f437cf754f | |||
| 2adb51e303 | |||
| a761ee3d4d | |||
| e1484d2ff8 | |||
| d82f9fe3f3 | |||
| 73ecdcee10 | |||
| c8e9afef61 | |||
| d866876d86 | |||
| 8ab000a5f4 | |||
| c8ede6fcc6 | |||
| 0d46b8e710 | |||
| 58f592c0d9 | |||
| e58d3abea8 | |||
| 4f818dd6f7 | |||
| d73806305a | |||
| 4e81e4fa4e | |||
| 8a6c4c93b6 | |||
| 7b99808cac | |||
| fab4b40e38 | |||
| 079f5eb5e5 | |||
| db0a220cda | |||
| 71bb1e8c44 |
121
CHANGELOG.md
121
CHANGELOG.md
@ -5,107 +5,50 @@ information on the list of deprecated flags and APIs please have a look at
|
||||
https://docs.docker.com/engine/deprecated/ where target removal dates can also
|
||||
be found.
|
||||
|
||||
**IMPORTANT**: You must stop all containers and plugins **BEFORE** upgrading to Docker CE 17.12.
|
||||
See related PR: [moby/moby#35812](https://github.com/moby/moby/pull/35812)
|
||||
## 17.10.0-ce (2017-10-17)
|
||||
|
||||
## 17.12.0-ce (2017-12-27)
|
||||
|
||||
## Known Issues
|
||||
* AWS logs batch size calculation [moby/moby#35726](https://github.com/moby/moby/pull/35726)
|
||||
* Health check no longer uses the container's working directory [moby/moby#35843](https://github.com/moby/moby/issues/35843)
|
||||
* Errors not returned from client in stack deploy configs [moby/moby#757](https://github.com/docker/cli/pull/757)
|
||||
* Daemon aborts when project quota fails [moby/moby#35827](https://github.com/moby/moby/pull/35827)
|
||||
* Docker cannot use memory limit when using systemd options [moby/moby#35123](https://github.com/moby/moby/issues/35123)
|
||||
IMPORTANT: Starting with this release, `docker service create`, `docker service update`,
|
||||
`docker service scale` and `docker service rollback` use non-detached mode as default,
|
||||
use `--detach` to keep the old behaviour.
|
||||
|
||||
### Builder
|
||||
|
||||
- Fix build cache hash for broken symlink [moby/moby#34271](https://github.com/moby/moby/pull/34271)
|
||||
- Fix long stream sync [moby/moby#35404](https://github.com/moby/moby/pull/35404)
|
||||
- Fix dockerfile parser failing silently on long tokens [moby/moby#35429](https://github.com/moby/moby/pull/35429)
|
||||
* Reset uid/gid to 0 in uploaded build context to share build cache with other clients [docker/cli#513](https://github.com/docker/cli/pull/513)
|
||||
+ Add support for `ADD` urls without any sub path [moby/moby#34217](https://github.com/moby/moby/pull/34217)
|
||||
|
||||
### Client
|
||||
|
||||
* Remove secret/config duplication in cli/compose [docker/cli#671](https://github.com/docker/cli/pull/671)
|
||||
* Add `--local` flag to `docker trust sign` [docker/cli#575](https://github.com/docker/cli/pull/575)
|
||||
* Add `docker trust inspect` [docker/cli#694](https://github.com/docker/cli/pull/694)
|
||||
+ Add `name` field to secrets and configs to allow interpolation in Compose files [docker/cli#668](https://github.com/docker/cli/pull/668)
|
||||
+ Add `--isolation` for setting swarm service isolation mode [docker/cli#426](https://github.com/docker/cli/pull/426)
|
||||
* Remove deprecated "daemon" subcommand [docker/cli#689](https://github.com/docker/cli/pull/689)
|
||||
- Fix behaviour of `rmi -f` with unexpected errors [docker/cli#654](https://github.com/docker/cli/pull/654)
|
||||
* Integrated Generic resource in service create [docker/cli#429](https://github.com/docker/cli/pull/429)
|
||||
- Fix external networks in stacks [docker/cli#743](https://github.com/docker/cli/pull/743)
|
||||
* Remove support for referencing images by image shortid [docker/cli#753](https://github.com/docker/cli/pull/753) and [moby/moby#35790](https://github.com/moby/moby/pull/35790)
|
||||
* Use commit-sha instead of tag for containerd [moby/moby#35770](https://github.com/moby/moby/pull/35770)
|
||||
|
||||
### Documentation
|
||||
|
||||
* Update API version history for 1.35 [moby/moby#35724](https://github.com/moby/moby/pull/35724)
|
||||
|
||||
### Logging
|
||||
|
||||
* Logentries driver line-only=true []byte output fix [moby/moby#35612](https://github.com/moby/moby/pull/35612)
|
||||
* Logentries line-only logopt fix to maintain backwards compatibility [moby/moby#35628](https://github.com/moby/moby/pull/35628)
|
||||
+ Add `--until` flag for docker logs [moby/moby#32914](https://github.com/moby/moby/pull/32914)
|
||||
+ Add gelf log driver plugin to Windows build [moby/moby#35073](https://github.com/moby/moby/pull/35073)
|
||||
* Set timeout on splunk batch send [moby/moby#35496](https://github.com/moby/moby/pull/35496)
|
||||
* Update Graylog2/go-gelf [moby/moby#35765](https://github.com/moby/moby/pull/35765)
|
||||
* Move output of `docker stack rm` to stdout [docker/cli#491](https://github.com/docker/cli/pull/491)
|
||||
* Use natural sort secrets and configs in cli [docker/cli#307](https://github.com/docker/cli/pull/307)
|
||||
* Use non-detached mode as default for `docker service` commands [docker/cli#525](https://github.com/docker/cli/pull/525)
|
||||
* Set APIVersion on the client, even when Ping fails [docker/cli#546](https://github.com/docker/cli/pull/546)
|
||||
- Fix loader error with different build syntax in `docker stack deploy` [docker/cli#544](https://github.com/docker/cli/pull/544)
|
||||
* Change the default output format for `docker container stats` to show `CONTAINER ID` and `NAME` [docker/cli#565](https://github.com/docker/cli/pull/565)
|
||||
+ Add `--no-trunc` flag to `docker container stats` [docker/cli#565](https://github.com/docker/cli/pull/565)
|
||||
+ Add experimental `docker trust`: `view`, `revoke`, `sign` subcommands [docker/cli#472](https://github.com/docker/cli/pull/472)
|
||||
- Various doc and shell completion fixes [docker/cli#610](https://github.com/docker/cli/pull/610) [docker/cli#611](https://github.com/docker/cli/pull/611) [docker/cli#618](https://github.com/docker/cli/pull/618) [docker/cli#580](https://github.com/docker/cli/pull/580) [docker/cli#598](https://github.com/docker/cli/pull/698) [docker/cli#603](https://github.com/docker/cli/pull/603)
|
||||
|
||||
### Networking
|
||||
|
||||
* Move load balancer sandbox creation/deletion into libnetwork [moby/moby#35422](https://github.com/moby/moby/pull/35422)
|
||||
* Only chown network files within container metadata [moby/moby#34224](https://github.com/moby/moby/pull/34224)
|
||||
* Restore error type in FindNetwork [moby/moby#35634](https://github.com/moby/moby/pull/35634)
|
||||
- Fix consumes MIME type for NetworkConnect [moby/moby#35542](https://github.com/moby/moby/pull/35542)
|
||||
+ Added support for persisting Windows network driver specific options [moby/moby#35563](https://github.com/moby/moby/pull/35563)
|
||||
- Fix timeout on netlink sockets and watchmiss leak [moby/moby#35677](https://github.com/moby/moby/pull/35677)
|
||||
+ New daemon config for networking diagnosis [moby/moby#35677](https://github.com/moby/moby/pull/35677)
|
||||
- Clean up node management logic [docker/libnetwork#2036](https://github.com/docker/libnetwork/pull/2036)
|
||||
- Allocate VIPs when endpoints are restored [docker/swarmkit#2474](https://github.com/docker/swarmkit/pull/2474)
|
||||
* Enabling ILB/ELB on windows using per-node, per-network LB endpoint [moby/moby#34674](https://github.com/moby/moby/pull/34674)
|
||||
* Overlay fix for transient IP reuse [docker/libnetwork#1935](https://github.com/docker/libnetwork/pull/1935)
|
||||
* Serializing bitseq alloc [docker/libnetwork#1788](https://github.com/docker/libnetwork/pull/1788)
|
||||
- Disable hostname lookup on chain exists check [docker/libnetwork#1974](https://github.com/docker/libnetwork/pull/1974)
|
||||
|
||||
### Runtime
|
||||
|
||||
* Update to containerd v1.0.0 [moby/moby#35707](https://github.com/moby/moby/pull/35707)
|
||||
* Have VFS graphdriver use accelerated in-kernel copy [moby/moby#35537](https://github.com/moby/moby/pull/35537)
|
||||
* Introduce `workingdir` option for docker exec [moby/moby#35661](https://github.com/moby/moby/pull/35661)
|
||||
* Bump Go to 1.9.2 [moby/moby#33892](https://github.com/moby/moby/pull/33892) [docker/cli#716](https://github.com/docker/cli/pull/716)
|
||||
* `/dev` should not be readonly with `--readonly` flag [moby/moby#35344](https://github.com/moby/moby/pull/35344)
|
||||
+ Add custom build-time Graphdrivers priority list [moby/moby#35522](https://github.com/moby/moby/pull/35522)
|
||||
* LCOW: CLI changes to add platform flag - pull, run, create and build [docker/cli#474](https://github.com/docker/cli/pull/474)
|
||||
* Fix width/height on Windoes for `docker exec` [moby/moby#35631](https://github.com/moby/moby/pull/35631)
|
||||
* Detect overlay2 support on pre-4.0 kernels [moby/moby#35527](https://github.com/moby/moby/pull/35527)
|
||||
* Devicemapper: remove container rootfs mountPath after umount [moby/moby#34573](https://github.com/moby/moby/pull/34573)
|
||||
* Disallow overlay/overlay2 on top of NFS [moby/moby#35483](https://github.com/moby/moby/pull/35483)
|
||||
- Fix potential panic during plugin set. [moby/moby#35632](https://github.com/moby/moby/pull/35632)
|
||||
- Fix some issues with locking on the container [moby/moby#35501](https://github.com/moby/moby/pull/35501)
|
||||
- Fixup some issues with plugin refcounting [moby/moby#35265](https://github.com/moby/moby/pull/35265)
|
||||
+ Add missing lock in ProcessEvent [moby/moby#35516](https://github.com/moby/moby/pull/35516)
|
||||
+ Add vfs quota support [moby/moby#35231](https://github.com/moby/moby/pull/35231)
|
||||
* Skip empty directories on prior graphdriver detection [moby/moby#35528](https://github.com/moby/moby/pull/35528)
|
||||
* Skip xfs quota tests when running in user namespace [moby/moby#35526](https://github.com/moby/moby/pull/35526)
|
||||
+ Added SubSecondPrecision to config option. [moby/moby#35529](https://github.com/moby/moby/pull/35529)
|
||||
* Update fsnotify to fix deadlock in removing watch [moby/moby#35453](https://github.com/moby/moby/pull/35453)
|
||||
- Fix "duplicate mount point" when `--tmpfs /dev/shm` is used [moby/moby#35467](https://github.com/moby/moby/pull/35467)
|
||||
- Fix honoring tmpfs-size for user `/dev/shm` mount [moby/moby#35316](https://github.com/moby/moby/pull/35316)
|
||||
- Fix EBUSY errors under overlayfs and v4.13+ kernels [moby/moby#34948](https://github.com/moby/moby/pull/34948)
|
||||
* Container: protect health monitor channel [moby/moby#35482](https://github.com/moby/moby/pull/35482)
|
||||
* Container: protect the health status with mutex [moby/moby#35517](https://github.com/moby/moby/pull/35517)
|
||||
* Container: update real-time resources [moby/moby#33731](https://github.com/moby/moby/pull/33731)
|
||||
* Create labels when volume exists only remotely [moby/moby#34896](https://github.com/moby/moby/pull/34896)
|
||||
- Fix leaking container/exec state [moby/moby#35484](https://github.com/moby/moby/pull/35484)
|
||||
* Disallow using legacy (v1) registries [moby/moby#35751](https://github.com/moby/moby/pull/35751) and [docker/cli#747](https://github.com/docker/cli/pull/747)
|
||||
- Windows: Fix case insensitive filename matching against builder cache [moby/moby#35793](https://github.com/moby/moby/pull/35793)
|
||||
- Fix race conditions around process handling and error checks [moby/moby#35809](https://github.com/moby/moby/pull/35809)
|
||||
* Ensure containers are stopped on daemon startup [moby/moby#35805](https://github.com/moby/moby/pull/35805)
|
||||
* Follow containerd namespace conventions [moby/moby#35812](https://github.com/moby/moby/pull/35812)
|
||||
* LCOW: Add UVM debugability by grabbing logs before tear-down [moby/moby#34846](https://github.com/moby/moby/pull/34846)
|
||||
* LCOW: Prepare work for bind mounts [moby/moby#34258](https://github.com/moby/moby/pull/34258)
|
||||
* LCOW: Support for docker cp, ADD/COPY on build [moby/moby#34252](https://github.com/moby/moby/pull/34252)
|
||||
* LCOW: VHDX boot to readonly [moby/moby#34754](https://github.com/moby/moby/pull/34754)
|
||||
* Volume: evaluate symlinks before relabeling mount source [moby/moby#34792](https://github.com/moby/moby/pull/34792)
|
||||
- Fixing ‘docker cp’ to allow new target file name in a host symlinked directory [moby/moby#31993](https://github.com/moby/moby/pull/31993)
|
||||
+ Add support for Windows version filtering on pull [moby/moby#35090](https://github.com/moby/moby/pull/35090)
|
||||
|
||||
### Swarm Mode
|
||||
### Swarm mode
|
||||
|
||||
+ Added support for swarm service isolation mode [moby/moby#34424](https://github.com/moby/moby/pull/34424)
|
||||
- Fix task clean up for tasks that are complete [docker/swarmkit#2477](https://github.com/docker/swarmkit/pull/2477)
|
||||
|
||||
### Packaging
|
||||
|
||||
+ Add Packaging for Fedora 27 [docker/docker-ce-packaging#59](https://github.com/docker/docker-ce-packaging/pull/59)
|
||||
* Change default versioning scheme to 0.0.0-dev unless specified for packaging [docker/docker-ce-packaging#67](https://github.com/docker/docker-ce-packaging/pull/67)
|
||||
* Pass Version to engine static builds [docker/docker-ce-packaging#70](https://github.com/docker/docker-ce-packaging/pull/70)
|
||||
+ Added support for aarch64 on Debian (stretch/jessie) and Ubuntu Zesty or newer [docker/docker-ce-packaging#35](https://github.com/docker/docker-ce-packaging/pull/35)
|
||||
* Produce an error if `docker swarm init --force-new-cluster` is executed on worker nodes [moby/moby#34881](https://github.com/moby/moby/pull/34881)
|
||||
+ Add support for `.Node.Hostname` templating in swarm services [moby/moby#34686](https://github.com/moby/moby/pull/34686)
|
||||
* Increase gRPC request timeout to 20 seconds for sending snapshots [docker/swarmkit#2391](https://github.com/docker/swarmkit/pull/2391)
|
||||
- Do not filter nodes if logdriver is set to `none` [docker/swarmkit#2396](https://github.com/docker/swarmkit/pull/2396)
|
||||
+ Adding ipam options to ipam driver requests [docker/swarmkit#2324](https://github.com/docker/swarmkit/pull/2324)
|
||||
|
||||
2
Makefile
2
Makefile
@ -1,7 +1,7 @@
|
||||
CLI_DIR:=$(CURDIR)/components/cli
|
||||
ENGINE_DIR:=$(CURDIR)/components/engine
|
||||
PACKAGING_DIR:=$(CURDIR)/components/packaging
|
||||
MOBY_COMPONENTS_SHA=ab7c118272b02d8672dc0255561d0c4015979780
|
||||
MOBY_COMPONENTS_SHA=f79265f1412af0a68aadd11e1d2f374446f3681b
|
||||
MOBY_COMPONENTS_URL=https://raw.githubusercontent.com/shykes/moby-extras/$(MOBY_COMPONENTS_SHA)/cmd/moby-components
|
||||
MOBY_COMPONENTS=.helpers/moby-components-$(MOBY_COMPONENTS_SHA)
|
||||
VERSION=$(shell cat VERSION)
|
||||
|
||||
4
components/cli/.github/CODEOWNERS
vendored
4
components/cli/.github/CODEOWNERS
vendored
@ -1,9 +1,9 @@
|
||||
# GitHub code owners
|
||||
# Github code owners
|
||||
# See https://github.com/blog/2392-introducing-code-owners
|
||||
|
||||
cli/command/stack/** @dnephin @vdemeester
|
||||
cli/compose/** @dnephin @vdemeester
|
||||
contrib/completion/bash/** @albers
|
||||
contrib/completion/zsh/** @sdurrheimer
|
||||
docs/** @mistyhacks @vdemeester @thaJeztah
|
||||
docs/** @mstanleyjones @vdemeester @thaJeztah
|
||||
scripts/** @dnephin
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
[people.cpuguy83]
|
||||
Name = "Brian Goff"
|
||||
Email = "cpuguy83@gmail.com"
|
||||
GitHub = "cpuguy83"
|
||||
Github = "cpuguy83"
|
||||
|
||||
[people.crosbymichael]
|
||||
Name = "Michael Crosby"
|
||||
@ -133,7 +133,7 @@
|
||||
[people.misty]
|
||||
Name = "Misty Stanley-Jones"
|
||||
Email = "misty@docker.com"
|
||||
GitHub = "mistyhacks"
|
||||
GitHub = "mstanleyjones"
|
||||
|
||||
[people.mlaventure]
|
||||
Name = "Kenfe-Mickaël Laventure"
|
||||
|
||||
@ -1 +1 @@
|
||||
17.12.0-ce
|
||||
17.10.0-ce
|
||||
|
||||
@ -17,7 +17,6 @@ func SetupRootCommand(rootCmd *cobra.Command) {
|
||||
cobra.AddTemplateFunc("operationSubCommands", operationSubCommands)
|
||||
cobra.AddTemplateFunc("managementSubCommands", managementSubCommands)
|
||||
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
|
||||
cobra.AddTemplateFunc("useLine", UseLine)
|
||||
|
||||
rootCmd.SetUsageTemplate(usageTemplate)
|
||||
rootCmd.SetHelpTemplate(helpTemplate)
|
||||
@ -26,7 +25,6 @@ func SetupRootCommand(rootCmd *cobra.Command) {
|
||||
|
||||
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
||||
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
|
||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||
}
|
||||
|
||||
// FlagErrorFunc prints an error message which matches the format of the
|
||||
@ -99,19 +97,9 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
return cmds
|
||||
}
|
||||
|
||||
// UseLine returns the usage line for a command. This implementation is different
|
||||
// from the default Command.UseLine in that it does not add a `[flags]` to the
|
||||
// end of the line.
|
||||
func UseLine(cmd *cobra.Command) string {
|
||||
if cmd.HasParent() {
|
||||
return cmd.Parent().CommandPath() + " " + cmd.Use
|
||||
}
|
||||
return cmd.Use
|
||||
}
|
||||
|
||||
var usageTemplate = `Usage:
|
||||
|
||||
{{- if not .HasSubCommands}} {{ useLine . }}{{end}}
|
||||
{{- if not .HasSubCommands}} {{.UseLine}}{{end}}
|
||||
{{- if .HasSubCommands}} {{ .CommandPath}} COMMAND{{end}}
|
||||
|
||||
{{ .Short | trim }}
|
||||
|
||||
@ -9,11 +9,11 @@ import (
|
||||
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
||||
func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "checkpoint",
|
||||
Short: "Manage checkpoints",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"experimental": "", "version": "1.25"},
|
||||
Use: "checkpoint",
|
||||
Short: "Manage checkpoints",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"experimental": "", "version": "1.25"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newCreateCommand(dockerCli),
|
||||
|
||||
@ -18,11 +18,11 @@ import (
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary"
|
||||
notaryclient "github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary"
|
||||
notaryclient "github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/passphrase"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -207,8 +207,7 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
|
||||
return client.NewClient(host, verStr, httpClient, customHeaders)
|
||||
}
|
||||
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
||||
var host string
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
host = os.Getenv("DOCKER_HOST")
|
||||
@ -218,7 +217,8 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
|
||||
return "", errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
return dopts.ParseHost(tlsOptions != nil, host)
|
||||
host, err = dopts.ParseHost(tlsOptions != nil, host)
|
||||
return
|
||||
}
|
||||
|
||||
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
||||
|
||||
@ -8,13 +8,14 @@ import (
|
||||
)
|
||||
|
||||
// NewConfigCommand returns a cobra command for `config` subcommands
|
||||
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewConfigCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage Docker configs",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.30"},
|
||||
Use: "config",
|
||||
Short: "Manage Docker configs",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.30"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newConfigListCommand(dockerCli),
|
||||
|
||||
@ -12,15 +12,12 @@ import (
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
|
||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
|
||||
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
|
||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
@ -74,24 +71,3 @@ func (f *fakeClient) Info(_ context.Context) (types.Info, error) {
|
||||
}
|
||||
return types.Info{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, container, path string) (types.ContainerPathStat, error) {
|
||||
if f.containerStatPathFunc != nil {
|
||||
return f.containerStatPathFunc(container, path)
|
||||
}
|
||||
return types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
if f.containerCopyFromFunc != nil {
|
||||
return f.containerCopyFromFunc(container, srcPath)
|
||||
}
|
||||
return nil, types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerLogs(_ context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
if f.logFunc != nil {
|
||||
return f.logFunc(container, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
// NewContainerCommand returns a cobra command for `container` subcommands
|
||||
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "container",
|
||||
Short: "Manage containers",
|
||||
|
||||
@ -22,7 +22,7 @@ type commitOptions struct {
|
||||
}
|
||||
|
||||
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
||||
func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options commitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -51,7 +51,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCommit(dockerCli command.Cli, options *commitOptions) error {
|
||||
func runCommit(dockerCli *command.DockerCli, options *commitOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
name := options.container
|
||||
|
||||
@ -26,21 +26,17 @@ type copyOptions struct {
|
||||
type copyDirection int
|
||||
|
||||
const (
|
||||
fromContainer copyDirection = 1 << iota
|
||||
fromContainer copyDirection = (1 << iota)
|
||||
toContainer
|
||||
acrossContainers = fromContainer | toContainer
|
||||
)
|
||||
|
||||
type cpConfig struct {
|
||||
followLink bool
|
||||
copyUIDGID bool
|
||||
sourcePath string
|
||||
destPath string
|
||||
container string
|
||||
}
|
||||
|
||||
// NewCopyCommand creates a new `docker cp` command
|
||||
func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewCopyCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts copyOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -69,57 +65,58 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCopy(dockerCli command.Cli, opts copyOptions) error {
|
||||
func runCopy(dockerCli *command.DockerCli, opts copyOptions) error {
|
||||
srcContainer, srcPath := splitCpArg(opts.source)
|
||||
destContainer, destPath := splitCpArg(opts.destination)
|
||||
|
||||
copyConfig := cpConfig{
|
||||
followLink: opts.followLink,
|
||||
copyUIDGID: opts.copyUIDGID,
|
||||
sourcePath: srcPath,
|
||||
destPath: destPath,
|
||||
}
|
||||
dstContainer, dstPath := splitCpArg(opts.destination)
|
||||
|
||||
var direction copyDirection
|
||||
if srcContainer != "" {
|
||||
direction |= fromContainer
|
||||
copyConfig.container = srcContainer
|
||||
}
|
||||
if destContainer != "" {
|
||||
if dstContainer != "" {
|
||||
direction |= toContainer
|
||||
copyConfig.container = destContainer
|
||||
}
|
||||
|
||||
cpParam := &cpConfig{
|
||||
followLink: opts.followLink,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
switch direction {
|
||||
case fromContainer:
|
||||
return copyFromContainer(ctx, dockerCli, copyConfig)
|
||||
return copyFromContainer(ctx, dockerCli, srcContainer, srcPath, dstPath, cpParam)
|
||||
case toContainer:
|
||||
return copyToContainer(ctx, dockerCli, copyConfig)
|
||||
return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam, opts.copyUIDGID)
|
||||
case acrossContainers:
|
||||
// Copying between containers isn't supported.
|
||||
return errors.New("copying between containers is not supported")
|
||||
default:
|
||||
// User didn't specify any container.
|
||||
return errors.New("must specify at least one container source")
|
||||
}
|
||||
}
|
||||
|
||||
func statContainerPath(ctx context.Context, dockerCli *command.DockerCli, containerName, path string) (types.ContainerPathStat, error) {
|
||||
return dockerCli.Client().ContainerStatPath(ctx, containerName, path)
|
||||
}
|
||||
|
||||
func resolveLocalPath(localPath string) (absPath string, err error) {
|
||||
if absPath, err = filepath.Abs(localPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
|
||||
}
|
||||
|
||||
func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
||||
dstPath := copyConfig.destPath
|
||||
srcPath := copyConfig.sourcePath
|
||||
|
||||
func copyFromContainer(ctx context.Context, dockerCli *command.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
|
||||
if dstPath != "-" {
|
||||
// Get an absolute destination path.
|
||||
dstPath, err = resolveLocalPath(dstPath)
|
||||
@ -128,11 +125,10 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
}
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
// if client requests to follow symbol link, then must decide target file to be copied
|
||||
var rebaseName string
|
||||
if copyConfig.followLink {
|
||||
srcStat, err := client.ContainerStatPath(ctx, copyConfig.container, srcPath)
|
||||
if cpParam.followLink {
|
||||
srcStat, err := statContainerPath(ctx, dockerCli, srcContainer, srcPath)
|
||||
|
||||
// If the destination is a symbolic link, we should follow it.
|
||||
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
|
||||
@ -149,17 +145,20 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
|
||||
}
|
||||
|
||||
content, stat, err := client.CopyFromContainer(ctx, copyConfig.container, srcPath)
|
||||
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, srcContainer, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
|
||||
if dstPath == "-" {
|
||||
_, err = io.Copy(dockerCli.Out(), content)
|
||||
// Send the response to STDOUT.
|
||||
_, err = io.Copy(os.Stdout, content)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare source copy info.
|
||||
srcInfo := archive.CopyInfo{
|
||||
Path: srcPath,
|
||||
Exists: true,
|
||||
@ -172,17 +171,13 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
|
||||
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
|
||||
}
|
||||
// See comments in the implementation of `archive.CopyTo` for exactly what
|
||||
// goes into deciding how and whether the source archive needs to be
|
||||
// altered for the correct copy behavior.
|
||||
return archive.CopyTo(preArchive, srcInfo, dstPath)
|
||||
}
|
||||
|
||||
// In order to get the copy behavior right, we need to know information
|
||||
// about both the source and destination. The API is a simple tar
|
||||
// archive/extract API but we can use the stat info header about the
|
||||
// destination to be more informed about exactly what the destination is.
|
||||
func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
||||
srcPath := copyConfig.sourcePath
|
||||
dstPath := copyConfig.destPath
|
||||
|
||||
func copyToContainer(ctx context.Context, dockerCli *command.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig, copyUIDGID bool) (err error) {
|
||||
if srcPath != "-" {
|
||||
// Get an absolute source path.
|
||||
srcPath, err = resolveLocalPath(srcPath)
|
||||
@ -191,10 +186,14 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
}
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
// In order to get the copy behavior right, we need to know information
|
||||
// about both the source and destination. The API is a simple tar
|
||||
// archive/extract API but we can use the stat info header about the
|
||||
// destination to be more informed about exactly what the destination is.
|
||||
|
||||
// Prepare destination copy info by stat-ing the container path.
|
||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
||||
dstStat, err := client.ContainerStatPath(ctx, copyConfig.container, dstPath)
|
||||
dstStat, err := statContainerPath(ctx, dockerCli, dstContainer, dstPath)
|
||||
|
||||
// If the destination is a symbolic link, we should evaluate it.
|
||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||
@ -206,7 +205,7 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
}
|
||||
|
||||
dstInfo.Path = linkTarget
|
||||
dstStat, err = client.ContainerStatPath(ctx, copyConfig.container, linkTarget)
|
||||
dstStat, err = statContainerPath(ctx, dockerCli, dstContainer, linkTarget)
|
||||
}
|
||||
|
||||
// Ignore any error and assume that the parent directory of the destination
|
||||
@ -225,14 +224,15 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
)
|
||||
|
||||
if srcPath == "-" {
|
||||
// Use STDIN.
|
||||
content = os.Stdin
|
||||
resolvedDstPath = dstInfo.Path
|
||||
if !dstInfo.IsDir {
|
||||
return errors.Errorf("destination \"%s:%s\" must be a directory", copyConfig.container, dstPath)
|
||||
return errors.Errorf("destination \"%s:%s\" must be a directory", dstContainer, dstPath)
|
||||
}
|
||||
} else {
|
||||
// Prepare source copy info.
|
||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath, copyConfig.followLink)
|
||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -267,9 +267,10 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
|
||||
options := types.CopyToContainerOptions{
|
||||
AllowOverwriteDirWithFile: false,
|
||||
CopyUIDGID: copyConfig.copyUIDGID,
|
||||
CopyUIDGID: copyUIDGID,
|
||||
}
|
||||
return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
|
||||
|
||||
return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
|
||||
}
|
||||
|
||||
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/gotestyourself/gotestyourself/skip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRunCopyWithInvalidArguments(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
options copyOptions
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "copy between container",
|
||||
options: copyOptions{
|
||||
source: "first:/path",
|
||||
destination: "second:/path",
|
||||
},
|
||||
expectedErr: "copying between containers is not supported",
|
||||
},
|
||||
{
|
||||
doc: "copy without a container",
|
||||
options: copyOptions{
|
||||
source: "./source",
|
||||
destination: "./dest",
|
||||
},
|
||||
expectedErr: "must specify at least one container source",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
err := runCopy(test.NewFakeCli(nil), testcase.options)
|
||||
assert.EqualError(t, err, testcase.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToStdout(t *testing.T) {
|
||||
tarContent := "the tar content"
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
return ioutil.NopCloser(strings.NewReader(tarContent)), types.ContainerPathStat{}, nil
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: "-"}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tarContent, cli.OutBuffer().String())
|
||||
assert.Equal(t, "", cli.ErrBuffer().String())
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToFilesystem(t *testing.T) {
|
||||
destDir := fs.NewDir(t, "cp-test",
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: destDir.Path()}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", cli.OutBuffer().String())
|
||||
assert.Equal(t, "", cli.ErrBuffer().String())
|
||||
|
||||
content, err := ioutil.ReadFile(destDir.Join("file1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "content\n", string(content))
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.T) {
|
||||
destDir := fs.NewDir(t, "cp-test",
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
},
|
||||
}
|
||||
|
||||
options := copyOptions{
|
||||
source: "container:/path",
|
||||
destination: destDir.Join("missing", "foo"),
|
||||
}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
testutil.ErrorContains(t, err, destDir.Join("missing"))
|
||||
}
|
||||
|
||||
func TestSplitCpArg(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
path string
|
||||
os string
|
||||
expectedContainer string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
doc: "absolute path with colon",
|
||||
os: "linux",
|
||||
path: "/abs/path:withcolon",
|
||||
expectedPath: "/abs/path:withcolon",
|
||||
},
|
||||
{
|
||||
doc: "relative path with colon",
|
||||
path: "./relative:path",
|
||||
expectedPath: "./relative:path",
|
||||
},
|
||||
{
|
||||
doc: "absolute path with drive",
|
||||
os: "windows",
|
||||
path: `d:\abs\path`,
|
||||
expectedPath: `d:\abs\path`,
|
||||
},
|
||||
{
|
||||
doc: "no separator",
|
||||
path: "relative/path",
|
||||
expectedPath: "relative/path",
|
||||
},
|
||||
{
|
||||
doc: "with separator",
|
||||
path: "container:/opt/foo",
|
||||
expectedPath: "/opt/foo",
|
||||
expectedContainer: "container",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
skip.IfCondition(t, testcase.os != "" && testcase.os != runtime.GOOS)
|
||||
|
||||
container, path := splitCpArg(testcase.path)
|
||||
assert.Equal(t, testcase.expectedContainer, container)
|
||||
assert.Equal(t, testcase.expectedPath, path)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -21,8 +21,7 @@ import (
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
name string
|
||||
platform string
|
||||
name string
|
||||
}
|
||||
|
||||
// NewCreateCommand creates a new cobra.Command for `docker create`
|
||||
@ -52,7 +51,6 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// with hostname
|
||||
flags.Bool("help", false, "Print usage")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
copts = addFlags(flags)
|
||||
return cmd
|
||||
@ -64,7 +62,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions,
|
||||
reportError(dockerCli.Err(), "create", err.Error(), true)
|
||||
return cli.StatusError{StatusCode: 125}
|
||||
}
|
||||
response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name, opts.platform)
|
||||
response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -72,7 +70,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error {
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, image string, out io.Writer) error {
|
||||
ref, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -92,7 +90,6 @@ func pullImage(ctx context.Context, dockerCli command.Cli, image string, platfor
|
||||
|
||||
options := types.ImageCreateOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
|
||||
@ -158,7 +155,7 @@ func newCIDFile(path string) (*cidFile, error) {
|
||||
return &cidFile{path: path, file: f}, nil
|
||||
}
|
||||
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string, platform string) (*container.ContainerCreateCreatedBody, error) {
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string) (*container.ContainerCreateCreatedBody, error) {
|
||||
config := containerConfig.Config
|
||||
hostConfig := containerConfig.HostConfig
|
||||
networkingConfig := containerConfig.NetworkingConfig
|
||||
@ -197,11 +194,11 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
|
||||
|
||||
//if image not found try to pull it
|
||||
if err != nil {
|
||||
if apiclient.IsErrNotFound(err) && namedRef != nil {
|
||||
if apiclient.IsErrImageNotFound(err) && namedRef != nil {
|
||||
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||
|
||||
// we don't want to write to stdout anything apart from container.ID
|
||||
if err := pullImage(ctx, dockerCli, config.Image, platform, stderr); err != nil {
|
||||
if err := pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -107,7 +106,7 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
|
||||
},
|
||||
HostConfig: &container.HostConfig{},
|
||||
}
|
||||
body, err := createContainer(context.Background(), cli, config, "name", runtime.GOOS)
|
||||
body, err := createContainer(context.Background(), cli, config, "name")
|
||||
require.NoError(t, err)
|
||||
expected := container.ContainerCreateCreatedBody{ID: containerID}
|
||||
assert.Equal(t, expected, *body)
|
||||
|
||||
@ -14,7 +14,7 @@ type diffOptions struct {
|
||||
}
|
||||
|
||||
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
||||
func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewDiffCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts diffOptions
|
||||
|
||||
return &cobra.Command{
|
||||
@ -28,7 +28,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func runDiff(dockerCli command.Cli, opts *diffOptions) error {
|
||||
func runDiff(dockerCli *command.DockerCli, opts *diffOptions) error {
|
||||
if opts.container == "" {
|
||||
return errors.New("Container name cannot be empty")
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ type execOptions struct {
|
||||
user string
|
||||
privileged bool
|
||||
env opts.ListOpts
|
||||
workdir string
|
||||
container string
|
||||
command []string
|
||||
}
|
||||
@ -59,8 +58,6 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command")
|
||||
flags.VarP(&options.env, "env", "e", "Set environment variables")
|
||||
flags.SetAnnotation("env", "version", []string{"1.25"})
|
||||
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -125,10 +122,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
execStartCheck := types.ExecStartCheck{
|
||||
Tty: execConfig.Tty,
|
||||
}
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, *execConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -193,7 +187,6 @@ func parseExec(opts execOptions, configFile *configfile.ConfigFile) *types.ExecC
|
||||
Cmd: opts.command,
|
||||
Detach: opts.detach,
|
||||
Env: opts.env.GetAll(),
|
||||
WorkingDir: opts.workdir,
|
||||
}
|
||||
|
||||
// If -d is not set, attach to everything by default
|
||||
|
||||
@ -16,7 +16,7 @@ type exportOptions struct {
|
||||
}
|
||||
|
||||
// NewExportCommand creates a new `docker export` command
|
||||
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewExportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts exportOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -36,7 +36,7 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runExport(dockerCli command.Cli, opts exportOptions) error {
|
||||
func runExport(dockerCli *command.DockerCli, opts exportOptions) error {
|
||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ type inspectOptions struct {
|
||||
}
|
||||
|
||||
// newInspectCommand creates a new cobra.Command for `docker container inspect`
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -35,7 +35,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ type killOptions struct {
|
||||
}
|
||||
|
||||
// NewKillCommand creates a new cobra.Command for `docker kill`
|
||||
func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewKillCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts killOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -36,7 +36,7 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKill(dockerCli command.Cli, opts *killOptions) error {
|
||||
func runKill(dockerCli *command.DockerCli, opts *killOptions) error {
|
||||
var errs []string
|
||||
ctx := context.Background()
|
||||
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
|
||||
|
||||
@ -25,7 +25,7 @@ type psOptions struct {
|
||||
}
|
||||
|
||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
||||
func NewPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
options := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -51,7 +51,7 @@ func NewPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := *NewPsCommand(dockerCli)
|
||||
cmd.Aliases = []string{"ps", "list"}
|
||||
cmd.Use = "ls [OPTIONS]"
|
||||
@ -109,7 +109,7 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func runPs(dockerCli command.Cli, options *psOptions) error {
|
||||
func runPs(dockerCli *command.DockerCli, options *psOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
listOptions, err := buildContainerListOptions(options)
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
type logsOptions struct {
|
||||
follow bool
|
||||
since string
|
||||
until string
|
||||
timestamps bool
|
||||
details bool
|
||||
tail string
|
||||
@ -23,7 +22,7 @@ type logsOptions struct {
|
||||
}
|
||||
|
||||
// NewLogsCommand creates a new cobra.Command for `docker logs`
|
||||
func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts logsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,22 +38,19 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
|
||||
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.SetAnnotation("until", "version", []string{"1.35"})
|
||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
|
||||
flags.BoolVar(&opts.details, "details", false, "Show extra details provided to logs")
|
||||
flags.StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Since: opts.since,
|
||||
Until: opts.until,
|
||||
Timestamps: opts.timestamps,
|
||||
Follow: opts.follow,
|
||||
Tail: opts.tail,
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var logFn = func(expectedOut string) func(string, types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return func(container string, opts types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(strings.NewReader(expectedOut)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunLogs(t *testing.T) {
|
||||
inspectFn := func(containerID string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{
|
||||
Config: &container.Config{Tty: true},
|
||||
ContainerJSONBase: &types.ContainerJSONBase{State: &types.ContainerState{Running: false}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
options *logsOptions
|
||||
client fakeClient
|
||||
expectedError string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "successful logs",
|
||||
expectedOut: "foo",
|
||||
options: &logsOptions{},
|
||||
client: fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&testcase.client)
|
||||
|
||||
err := runLogs(cli, testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
testutil.ErrorContains(t, err, testcase.expectedError)
|
||||
} else {
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
assert.Equal(t, testcase.expectedOut, cli.OutBuffer().String())
|
||||
assert.Equal(t, testcase.expectedErr, cli.ErrBuffer().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -45,7 +45,10 @@ func TestValidateAttach(t *testing.T) {
|
||||
|
||||
// nolint: unparam
|
||||
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
|
||||
flags, copts := setupRunFlags()
|
||||
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
|
||||
flags.SetOutput(ioutil.Discard)
|
||||
flags.Usage = nil
|
||||
copts := addFlags(flags)
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -57,14 +60,6 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
|
||||
return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
|
||||
}
|
||||
|
||||
func setupRunFlags() (*pflag.FlagSet, *containerOptions) {
|
||||
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
|
||||
flags.SetOutput(ioutil.Discard)
|
||||
flags.Usage = nil
|
||||
copts := addFlags(flags)
|
||||
return flags, copts
|
||||
}
|
||||
|
||||
func parseMustError(t *testing.T, args string) {
|
||||
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
|
||||
assert.Error(t, err, args)
|
||||
@ -232,21 +227,20 @@ func TestParseWithMacAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFlagsParseWithMemory(t *testing.T) {
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--memory=invalid", "img", "cmd"}
|
||||
err := flags.Parse(args)
|
||||
testutil.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`)
|
||||
func TestParseWithMemory(t *testing.T) {
|
||||
invalidMemory := "--memory=invalid"
|
||||
_, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, invalidMemory)
|
||||
|
||||
_, hostconfig := mustParse(t, "--memory=1G")
|
||||
assert.Equal(t, int64(1073741824), hostconfig.Memory)
|
||||
}
|
||||
|
||||
func TestParseWithMemorySwap(t *testing.T) {
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--memory-swap=invalid", "img", "cmd"}
|
||||
err := flags.Parse(args)
|
||||
testutil.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`)
|
||||
invalidMemory := "--memory-swap=invalid"
|
||||
|
||||
_, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, invalidMemory)
|
||||
|
||||
_, hostconfig := mustParse(t, "--memory-swap=1G")
|
||||
assert.Equal(t, int64(1073741824), hostconfig.MemorySwap)
|
||||
@ -371,10 +365,7 @@ func TestParseDevice(t *testing.T) {
|
||||
|
||||
func TestParseModes(t *testing.T) {
|
||||
// pid ko
|
||||
flags, copts := setupRunFlags()
|
||||
args := []string{"--pid=container:", "img", "cmd"}
|
||||
require.NoError(t, flags.Parse(args))
|
||||
_, err := parse(flags, copts)
|
||||
_, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, "--pid: invalid PID mode")
|
||||
|
||||
// pid ok
|
||||
@ -394,18 +385,14 @@ func TestParseModes(t *testing.T) {
|
||||
if !hostconfig.UTSMode.Valid() {
|
||||
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFlagsParseShmSize(t *testing.T) {
|
||||
// shm-size ko
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--shm-size=a128m", "img", "cmd"}
|
||||
expectedErr := `invalid argument "a128m" for "--shm-size" flag: invalid size: 'a128m'`
|
||||
err := flags.Parse(args)
|
||||
expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
|
||||
_, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, expectedErr)
|
||||
|
||||
// shm-size ok
|
||||
_, hostconfig, _, err := parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
||||
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
||||
require.NoError(t, err)
|
||||
if hostconfig.ShmSize != 134217728 {
|
||||
t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
|
||||
|
||||
@ -16,7 +16,7 @@ type pauseOptions struct {
|
||||
}
|
||||
|
||||
// NewPauseCommand creates a new cobra.Command for `docker pause`
|
||||
func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPauseCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts pauseOptions
|
||||
|
||||
return &cobra.Command{
|
||||
@ -30,7 +30,7 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func runPause(dockerCli command.Cli, opts *pauseOptions) error {
|
||||
func runPause(dockerCli *command.DockerCli, opts *pauseOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -19,7 +19,7 @@ type portOptions struct {
|
||||
}
|
||||
|
||||
// NewPortCommand creates a new cobra.Command for `docker port`
|
||||
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPortCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts portOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -37,7 +37,7 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPort(dockerCli command.Cli, opts *portOptions) error {
|
||||
func runPort(dockerCli *command.DockerCli, opts *portOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
||||
|
||||
@ -35,7 +35,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -52,12 +52,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
return
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return
|
||||
}
|
||||
|
||||
if len(report.ContainersDeleted) > 0 {
|
||||
@ -68,7 +68,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
spaceReclaimed = report.SpaceReclaimed
|
||||
}
|
||||
|
||||
return spaceReclaimed, output, nil
|
||||
return
|
||||
}
|
||||
|
||||
// RunPrune calls the Container Prune API
|
||||
|
||||
@ -17,7 +17,7 @@ type renameOptions struct {
|
||||
}
|
||||
|
||||
// NewRenameCommand creates a new cobra.Command for `docker rename`
|
||||
func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRenameCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts renameOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -33,7 +33,7 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRename(dockerCli command.Cli, opts *renameOptions) error {
|
||||
func runRename(dockerCli *command.DockerCli, opts *renameOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
oldName := strings.TrimSpace(opts.oldName)
|
||||
|
||||
@ -20,7 +20,7 @@ type restartOptions struct {
|
||||
}
|
||||
|
||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
||||
func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts restartOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,7 +39,7 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRestart(dockerCli command.Cli, opts *restartOptions) error {
|
||||
func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error {
|
||||
ctx := context.Background()
|
||||
var errs []string
|
||||
var timeout *time.Duration
|
||||
|
||||
@ -21,7 +21,7 @@ type rmOptions struct {
|
||||
}
|
||||
|
||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
||||
func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRmCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -41,7 +41,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRm(dockerCli command.Cli, opts *rmOptions) error {
|
||||
func runRm(dockerCli *command.DockerCli, opts *rmOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -29,11 +29,10 @@ type runOptions struct {
|
||||
sigProxy bool
|
||||
name string
|
||||
detachKeys string
|
||||
platform string
|
||||
}
|
||||
|
||||
// NewRunCommand create a new `docker run` command
|
||||
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts runOptions
|
||||
var copts *containerOptions
|
||||
|
||||
@ -63,7 +62,6 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// with hostname
|
||||
flags.Bool("help", false, "Print usage")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
copts = addFlags(flags)
|
||||
return cmd
|
||||
@ -98,7 +96,7 @@ func isLocalhost(ip string) bool {
|
||||
return localhostIPRegexp.MatchString(ip)
|
||||
}
|
||||
|
||||
func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll())
|
||||
newEnv := []string{}
|
||||
for k, v := range proxyConfig {
|
||||
@ -119,7 +117,7 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
|
||||
func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
|
||||
config := containerConfig.Config
|
||||
hostConfig := containerConfig.HostConfig
|
||||
stdout, stderr := dockerCli.Out(), dockerCli.Err()
|
||||
@ -162,7 +160,7 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio
|
||||
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
|
||||
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name, opts.platform)
|
||||
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name)
|
||||
if err != nil {
|
||||
reportError(stderr, cmdPath, err.Error(), true)
|
||||
return runStartContainerErr(err)
|
||||
|
||||
@ -27,7 +27,7 @@ type startOptions struct {
|
||||
}
|
||||
|
||||
// NewStartCommand creates a new cobra.Command for `docker start`
|
||||
func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts startOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -53,7 +53,7 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runStart(dockerCli command.Cli, opts *startOptions) error {
|
||||
func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
|
||||
if opts.attach || opts.openStdin {
|
||||
@ -181,7 +181,7 @@ func runStart(dockerCli command.Cli, opts *startOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error {
|
||||
func startContainersWithoutAttachments(ctx context.Context, dockerCli *command.DockerCli, containers []string) error {
|
||||
var failedContainers []string
|
||||
for _, container := range containers {
|
||||
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
|
||||
|
||||
@ -27,7 +27,7 @@ type statsOptions struct {
|
||||
}
|
||||
|
||||
// NewStatsCommand creates a new cobra.Command for `docker stats`
|
||||
func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts statsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -51,7 +51,7 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// runStats displays a live stream of resource usage statistics for one or more containers.
|
||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||
// nolint: gocyclo
|
||||
func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
||||
showAll := len(opts.containers) == 0
|
||||
closeChan := make(chan error)
|
||||
|
||||
|
||||
@ -200,8 +200,7 @@ func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
||||
return 0.00
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
var blkRead, blkWrite uint64
|
||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(bioEntry.Op) {
|
||||
case "read":
|
||||
@ -210,7 +209,7 @@ func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
blkWrite = blkWrite + bioEntry.Value
|
||||
}
|
||||
}
|
||||
return blkRead, blkWrite
|
||||
return
|
||||
}
|
||||
|
||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||
|
||||
@ -20,7 +20,7 @@ type stopOptions struct {
|
||||
}
|
||||
|
||||
// NewStopCommand creates a new cobra.Command for `docker stop`
|
||||
func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts stopOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,7 +39,7 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStop(dockerCli command.Cli, opts *stopOptions) error {
|
||||
func runStop(dockerCli *command.DockerCli, opts *stopOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var timeout *time.Duration
|
||||
|
||||
@ -18,7 +18,7 @@ type topOptions struct {
|
||||
}
|
||||
|
||||
// NewTopCommand creates a new cobra.Command for `docker top`
|
||||
func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewTopCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts topOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -38,7 +38,7 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runTop(dockerCli command.Cli, opts *topOptions) error {
|
||||
func runTop(dockerCli *command.DockerCli, opts *topOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, opts.args)
|
||||
|
||||
@ -16,7 +16,7 @@ type unpauseOptions struct {
|
||||
}
|
||||
|
||||
// NewUnpauseCommand creates a new cobra.Command for `docker unpause`
|
||||
func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewUnpauseCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts unpauseOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -31,7 +31,7 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUnpause(dockerCli command.Cli, opts *unpauseOptions) error {
|
||||
func runUnpause(dockerCli *command.DockerCli, opts *unpauseOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -35,7 +35,7 @@ type updateOptions struct {
|
||||
}
|
||||
|
||||
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
||||
func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options updateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -72,7 +72,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli command.Cli, options *updateOptions) error {
|
||||
func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
|
||||
var err error
|
||||
|
||||
if options.nFlag == 0 {
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||
if len(containerID) == 0 {
|
||||
// containerID can never be empty
|
||||
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
||||
@ -47,7 +47,7 @@ func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID s
|
||||
return statusC
|
||||
}
|
||||
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||
var removeErr error
|
||||
statusChan := make(chan int)
|
||||
exitCode := 125
|
||||
|
||||
@ -16,7 +16,7 @@ type waitOptions struct {
|
||||
}
|
||||
|
||||
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
||||
func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewWaitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts waitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -32,7 +32,7 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWait(dockerCli command.Cli, opts *waitOptions) error {
|
||||
func runWait(dockerCli *command.DockerCli, opts *waitOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -164,7 +165,7 @@ func (c *containerContext) Image() string {
|
||||
func (c *containerContext) Command() string {
|
||||
command := c.c.Command
|
||||
if c.trunc {
|
||||
command = Ellipsis(command, 20)
|
||||
command = stringutils.Ellipsis(command, 20)
|
||||
}
|
||||
return strconv.Quote(command)
|
||||
}
|
||||
@ -226,7 +227,7 @@ func (c *containerContext) Mounts() string {
|
||||
name = m.Name
|
||||
}
|
||||
if c.trunc {
|
||||
name = Ellipsis(name, 15)
|
||||
name = stringutils.Ellipsis(name, 15)
|
||||
}
|
||||
mounts = append(mounts, name)
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||
Source: "/a/path",
|
||||
},
|
||||
},
|
||||
}, true, "this-is-a-long…", ctx.Mounts},
|
||||
}, true, "this-is-a-lo...", ctx.Mounts},
|
||||
{types.Container{
|
||||
Mounts: []types.MountPoint{
|
||||
{
|
||||
|
||||
@ -118,11 +118,11 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
func (ctx *DiskUsageContext) verboseWrite() (err error) {
|
||||
// First images
|
||||
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
||||
@ -141,14 +141,14 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
}
|
||||
}
|
||||
|
||||
err := ctx.contextFormat(tmpl, &imageContext{
|
||||
err = ctx.contextFormat(tmpl, &imageContext{
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
trunc: true,
|
||||
i: *i,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newImageContext())
|
||||
@ -157,14 +157,17 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
||||
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, c := range ctx.Containers {
|
||||
// Don't display the virtual size
|
||||
c.SizeRootFs = 0
|
||||
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
|
||||
err = ctx.contextFormat(tmpl, &containerContext{
|
||||
trunc: true,
|
||||
c: *c,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newContainerContext())
|
||||
@ -173,18 +176,21 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
||||
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, v := range ctx.Volumes {
|
||||
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil {
|
||||
return err
|
||||
err = ctx.contextFormat(tmpl, &volumeContext{
|
||||
v: *v,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newVolumeContext())
|
||||
|
||||
// And build cache
|
||||
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
type diskUsageImagesContext struct {
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
// charWidth returns the number of horizontal positions a character occupies,
|
||||
// and is used to account for wide characters when displaying strings.
|
||||
//
|
||||
// In a broad sense, wide characters include East Asian Wide, East Asian Full-width,
|
||||
// (when not in East Asian context) see http://unicode.org/reports/tr11/.
|
||||
func charWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
||||
// For maxDisplayWidth of 1 and lower, no ellipsis is appended.
|
||||
// For maxDisplayWidth of 1, first char of string will return even if its width > 1.
|
||||
func Ellipsis(s string, maxDisplayWidth int) string {
|
||||
if maxDisplayWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
rs := []rune(s)
|
||||
if maxDisplayWidth == 1 {
|
||||
return string(rs[0])
|
||||
}
|
||||
|
||||
byteLen := len(s)
|
||||
if byteLen == utf8.RuneCountInString(s) {
|
||||
if byteLen <= maxDisplayWidth {
|
||||
return s
|
||||
}
|
||||
return string(rs[:maxDisplayWidth-1]) + "…"
|
||||
}
|
||||
|
||||
var (
|
||||
display []int
|
||||
displayWidth int
|
||||
)
|
||||
for _, r := range rs {
|
||||
cw := charWidth(r)
|
||||
displayWidth += cw
|
||||
display = append(display, displayWidth)
|
||||
}
|
||||
if displayWidth <= maxDisplayWidth {
|
||||
return s
|
||||
}
|
||||
for i := range display {
|
||||
if display[i] <= maxDisplayWidth-1 && display[i+1] > maxDisplayWidth-1 {
|
||||
return string(rs[:i+1]) + "…"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEllipsis(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
source string
|
||||
width int
|
||||
expected string
|
||||
}{
|
||||
{source: "t🐳ststring", width: 0, expected: ""},
|
||||
{source: "t🐳ststring", width: 1, expected: "t"},
|
||||
{source: "t🐳ststring", width: 2, expected: "t…"},
|
||||
{source: "t🐳ststring", width: 6, expected: "t🐳st…"},
|
||||
{source: "t🐳ststring", width: 20, expected: "t🐳ststring"},
|
||||
{source: "你好世界teststring", width: 0, expected: ""},
|
||||
{source: "你好世界teststring", width: 1, expected: "你"},
|
||||
{source: "你好世界teststring", width: 3, expected: "你…"},
|
||||
{source: "你好世界teststring", width: 6, expected: "你好…"},
|
||||
{source: "你好世界teststring", width: 20, expected: "你好世界teststring"},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, testcase.expected, Ellipsis(testcase.source, testcase.width))
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -92,7 +93,7 @@ func (c *historyContext) CreatedSince() string {
|
||||
func (c *historyContext) CreatedBy() string {
|
||||
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
|
||||
if c.trunc {
|
||||
return Ellipsis(createdBy, 45)
|
||||
return stringutils.Ellipsis(createdBy, 45)
|
||||
}
|
||||
return createdBy
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -95,7 +96,7 @@ func TestHistoryContext_CreatedBy(t *testing.T) {
|
||||
historyContext{
|
||||
h: image.HistoryResponseItem{CreatedBy: withTabs},
|
||||
trunc: true,
|
||||
}, Ellipsis(expected, 45), ctx.CreatedBy,
|
||||
}, stringutils.Ellipsis(expected, 45), ctx.CreatedBy,
|
||||
},
|
||||
}
|
||||
|
||||
@ -190,7 +191,7 @@ imageID3 24 hours ago /bin/bash ls
|
||||
imageID4 24 hours ago /bin/bash grep 183MB Hi
|
||||
`
|
||||
expectedTrunc := `IMAGE CREATED CREATED BY SIZE COMMENT
|
||||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && kar… 183MB Hi
|
||||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && k... 183MB Hi
|
||||
imageID2 24 hours ago /bin/bash echo 183MB Hi
|
||||
imageID3 24 hours ago /bin/bash ls 183MB Hi
|
||||
imageID4 24 hours ago /bin/bash grep 183MB Hi
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -79,7 +80,7 @@ func (c *pluginContext) Description() string {
|
||||
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
|
||||
desc = strings.Replace(desc, "\r", "", -1)
|
||||
if c.trunc {
|
||||
desc = Ellipsis(desc, 45)
|
||||
desc = stringutils.Ellipsis(desc, 45)
|
||||
}
|
||||
|
||||
return desc
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
registry "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -72,7 +73,7 @@ func (c *searchContext) Description() string {
|
||||
desc := strings.Replace(c.s.Description, "\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\r", " ", -1)
|
||||
if c.trunc {
|
||||
desc = Ellipsis(desc, 45)
|
||||
desc = stringutils.Ellipsis(desc, 45)
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -78,7 +79,7 @@ func TestSearchContextDescription(t *testing.T) {
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: longDescription},
|
||||
trunc: true,
|
||||
}, Ellipsis(longDescription, 45), ctx.Description},
|
||||
}, stringutils.Ellipsis(longDescription, 45), ctx.Description},
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: descriptionWReturns},
|
||||
trunc: false,
|
||||
@ -86,7 +87,7 @@ func TestSearchContextDescription(t *testing.T) {
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: descriptionWReturns},
|
||||
trunc: true,
|
||||
}, Ellipsis(longDescription, 45), ctx.Description},
|
||||
}, stringutils.Ellipsis(longDescription, 45), ctx.Description},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
|
||||
@ -64,7 +64,6 @@ type buildOptions struct {
|
||||
target string
|
||||
imageIDFile string
|
||||
stream bool
|
||||
platform string
|
||||
}
|
||||
|
||||
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
||||
@ -136,7 +135,6 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
command.AddPlatformFlag(flags, &options.platform)
|
||||
|
||||
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
|
||||
flags.SetAnnotation("squash", "experimental", nil)
|
||||
@ -307,8 +305,8 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||
}
|
||||
|
||||
// if up to this point nothing has set the context then we must have another
|
||||
// way for sending it(streaming) and set the context to the Dockerfile
|
||||
// if up to this point nothing has set the context then we must have have
|
||||
// another way for sending it(streaming) and set the context to the Dockerfile
|
||||
if dockerfileCtx != nil && buildCtx == nil {
|
||||
buildCtx = dockerfileCtx
|
||||
}
|
||||
@ -376,7 +374,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
ExtraHosts: options.extraHosts.GetAll(),
|
||||
Target: options.target,
|
||||
RemoteContext: remote,
|
||||
Platform: options.platform,
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
|
||||
@ -128,18 +128,18 @@ func getBuildSharedKey(dir string) (string, error) {
|
||||
return hex.EncodeToString(s[:]), nil
|
||||
}
|
||||
|
||||
func tryNodeIdentifier() string {
|
||||
out := cliconfig.Dir() // return config dir as default on permission error
|
||||
func tryNodeIdentifier() (out string) {
|
||||
out = cliconfig.Dir() // return config dir as default on permission error
|
||||
if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil {
|
||||
sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID")
|
||||
if _, err := os.Lstat(sessionFile); err != nil {
|
||||
if os.IsNotExist(err) { // create a new file with stored randomness
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return out
|
||||
return
|
||||
}
|
||||
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||
return out
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,5 +149,5 @@ func tryNodeIdentifier() string {
|
||||
return string(dt)
|
||||
}
|
||||
}
|
||||
return out
|
||||
return
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -65,12 +65,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
warning = allImageWarning
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
return
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ImagesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return
|
||||
}
|
||||
|
||||
if len(report.ImagesDeleted) > 0 {
|
||||
@ -85,7 +85,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
spaceReclaimed = report.SpaceReclaimed
|
||||
}
|
||||
|
||||
return spaceReclaimed, output, nil
|
||||
return
|
||||
}
|
||||
|
||||
// RunPrune calls the Image Prune API
|
||||
|
||||
@ -14,9 +14,8 @@ import (
|
||||
)
|
||||
|
||||
type pullOptions struct {
|
||||
remote string
|
||||
all bool
|
||||
platform string
|
||||
remote string
|
||||
all bool
|
||||
}
|
||||
|
||||
// NewPullCommand creates a new `docker pull` command
|
||||
@ -36,8 +35,6 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
|
||||
return cmd
|
||||
@ -66,9 +63,9 @@ func runPull(cli command.Cli, opts pullOptions) error {
|
||||
// Check if reference has a digest
|
||||
_, isCanonical := distributionRef.(reference.Canonical)
|
||||
if command.IsTrusted() && !isCanonical {
|
||||
err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform)
|
||||
err = trustedPull(ctx, cli, imgRefAndAuth)
|
||||
} else {
|
||||
err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform)
|
||||
err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "when fetching 'plugin'") {
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -57,13 +56,9 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
|
||||
}
|
||||
|
||||
var errs []string
|
||||
var fatalErr = false
|
||||
for _, img := range images {
|
||||
dels, err := client.ImageRemove(ctx, img, options)
|
||||
if err != nil {
|
||||
if !apiclient.IsErrNotFound(err) {
|
||||
fatalErr = true
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
for _, del := range dels {
|
||||
@ -78,7 +73,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
|
||||
|
||||
if len(errs) > 0 {
|
||||
msg := strings.Join(errs, "\n")
|
||||
if !opts.force || fatalErr {
|
||||
if !opts.force {
|
||||
return errors.New(msg)
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Err(), msg)
|
||||
|
||||
@ -13,18 +13,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type notFound struct {
|
||||
imageID string
|
||||
}
|
||||
|
||||
func (n notFound) Error() string {
|
||||
return fmt.Sprintf("Error: No such image: %s", n.imageID)
|
||||
}
|
||||
|
||||
func (n notFound) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestNewRemoveCommandAlias(t *testing.T) {
|
||||
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}))
|
||||
assert.True(t, cmd.HasAlias("rmi"))
|
||||
@ -43,15 +31,6 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
||||
name: "wrong args",
|
||||
expectedError: "requires at least 1 argument.",
|
||||
},
|
||||
{
|
||||
name: "ImageRemove fail with force option",
|
||||
args: []string{"-f", "image1"},
|
||||
expectedError: "error removing image",
|
||||
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||
assert.Equal(t, "image1", image)
|
||||
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ImageRemove fail",
|
||||
args: []string{"arg1"},
|
||||
@ -64,14 +43,12 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageRemoveFunc: tc.imageRemoveFunc,
|
||||
}))
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
})
|
||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageRemoveFunc: tc.imageRemoveFunc,
|
||||
}))
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +57,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
|
||||
expectedStderr string
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "Image Deleted",
|
||||
@ -91,16 +68,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Image not found with force option",
|
||||
name: "Image Deleted with force option",
|
||||
args: []string{"-f", "image1"},
|
||||
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||
assert.Equal(t, "image1", image)
|
||||
assert.Equal(t, true, options.Force)
|
||||
return []types.ImageDeleteResponseItem{}, notFound{"image1"}
|
||||
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
|
||||
},
|
||||
expectedStderr: "Error: No such image: image1",
|
||||
expectedErrMsg: "error removing image",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Image Untagged",
|
||||
args: []string{"image1"},
|
||||
@ -121,14 +96,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||
cmd := NewRemoveCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, tc.expectedStderr, cli.ErrBuffer().String())
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("remove-command-success.%s.golden", tc.name))
|
||||
})
|
||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||
cmd := NewRemoveCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
if tc.expectedErrMsg != "" {
|
||||
assert.Equal(t, tc.expectedErrMsg, cli.ErrBuffer().String())
|
||||
}
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("remove-command-success.%s.golden", tc.name))
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,11 +14,11 @@ import (
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -84,7 +84,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
if err := jsonmessage.DisplayJSONMessagesToStream(in, streams.Out(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(streams.Err(), "No tag specified, skipping trust metadata push")
|
||||
fmt.Fprintln(streams.Out(), "No tag specified, skipping trust metadata push")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -97,14 +97,16 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return errors.Errorf("no targets found, please provide a specific tag in order to sign it")
|
||||
fmt.Fprintln(streams.Out(), "No targets found, please provide a specific tag in order to sign it")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata")
|
||||
|
||||
repo, err := trust.GetNotaryRepository(streams.In(), streams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error establishing connection to trust repository")
|
||||
fmt.Fprintf(streams.Out(), "Error establishing connection to notary repository: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// get the latest repository metadata so we can figure out which roles to sign
|
||||
@ -144,11 +146,11 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
|
||||
fmt.Fprintf(streams.Out(), "Failed to sign %q:%s - %s\n", repoInfo.Name.Name(), tag, err.Error())
|
||||
return trust.NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(streams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
||||
fmt.Fprintf(streams.Out(), "Successfully signed %q:%s\n", repoInfo.Name.Name(), tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -180,7 +182,7 @@ func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.
|
||||
}
|
||||
|
||||
// trustedPull handles content trust pulling of an image
|
||||
func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, platform string) error {
|
||||
func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) error {
|
||||
refs, err := getTrustedPullTargets(cli, imgRefAndAuth)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -202,7 +204,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false, platform); err != nil {
|
||||
if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -221,7 +223,8 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
|
||||
func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) ([]target, error) {
|
||||
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref := imgRefAndAuth.Reference()
|
||||
@ -236,7 +239,7 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth)
|
||||
for _, tgt := range targets {
|
||||
t, err := convertTarget(tgt.Target)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.Err(), "Skipping target for %q\n", reference.FamiliarName(ref))
|
||||
fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref))
|
||||
continue
|
||||
}
|
||||
// Only list tags in the top level targets role or the releases delegation role - ignore
|
||||
@ -262,13 +265,13 @@ func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth)
|
||||
return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag()))
|
||||
}
|
||||
|
||||
logrus.Debugf("retrieving target for %s role", t.Role)
|
||||
logrus.Debugf("retrieving target for %s role\n", t.Role)
|
||||
r, err := convertTarget(t.Target)
|
||||
return []target{r}, err
|
||||
}
|
||||
|
||||
// imagePullPrivileged pulls the image and displays it to the output
|
||||
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool, platform string) error {
|
||||
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool) error {
|
||||
ref := reference.FamiliarString(imgRefAndAuth.Reference())
|
||||
|
||||
encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig())
|
||||
@ -280,8 +283,8 @@ func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth tru
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
All: all,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := cli.Client().ImagePull(ctx, ref, options)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -311,7 +314,8 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
|
||||
|
||||
notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
@ -328,6 +332,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
return reference.WithDigest(reference.TrimNamed(ref), r.digest)
|
||||
}
|
||||
|
||||
@ -350,7 +355,7 @@ func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canon
|
||||
familiarRef := reference.FamiliarString(ref)
|
||||
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
||||
|
||||
fmt.Fprintf(cli.Err(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
|
||||
fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
|
||||
|
||||
return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef)
|
||||
}
|
||||
|
||||
@ -8,11 +8,11 @@ import (
|
||||
"github.com/docker/cli/cli/trust"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/passphrase"
|
||||
"github.com/theupdateframework/notary/trustpinning"
|
||||
)
|
||||
|
||||
func unsetENV() {
|
||||
|
||||
@ -33,7 +33,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -50,12 +50,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return "", nil
|
||||
return
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().NetworksPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return
|
||||
}
|
||||
|
||||
if len(report.NetworksDeleted) > 0 {
|
||||
@ -65,7 +65,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e
|
||||
}
|
||||
}
|
||||
|
||||
return output, nil
|
||||
return
|
||||
}
|
||||
|
||||
// RunPrune calls the Network Prune API
|
||||
|
||||
@ -14,11 +14,11 @@ import (
|
||||
// NewNodeCommand returns a cobra command for `node` subcommands
|
||||
func NewNodeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Manage Swarm nodes",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.24"},
|
||||
Use: "node",
|
||||
Short: "Manage Swarm nodes",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.24"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newDemoteCommand(dockerCli),
|
||||
|
||||
@ -7,13 +7,14 @@ import (
|
||||
)
|
||||
|
||||
// NewPluginCommand returns a cobra command for `plugin` subcommands
|
||||
func NewPluginCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewPluginCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "plugin",
|
||||
Short: "Manage plugins",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Use: "plugin",
|
||||
Short: "Manage plugins",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
|
||||
@ -63,7 +63,7 @@ type pluginCreateOptions struct {
|
||||
compress bool
|
||||
}
|
||||
|
||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
options := pluginCreateOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -84,7 +84,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCreate(dockerCli command.Cli, options pluginCreateOptions) error {
|
||||
func runCreate(dockerCli *command.DockerCli, options pluginCreateOptions) error {
|
||||
var (
|
||||
createCtx io.ReadCloser
|
||||
err error
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newDisableCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newDisableCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var force bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -27,7 +27,7 @@ func newDisableCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDisable(dockerCli command.Cli, name string, force bool) error {
|
||||
func runDisable(dockerCli *command.DockerCli, name string, force bool) error {
|
||||
if err := dockerCli.Client().PluginDisable(context.Background(), name, types.PluginDisableOptions{Force: force}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ type enableOpts struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func newEnableCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newEnableCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts enableOpts
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -34,7 +34,7 @@ func newEnableCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEnable(dockerCli command.Cli, opts *enableOpts) error {
|
||||
func runEnable(dockerCli *command.DockerCli, opts *enableOpts) error {
|
||||
name := opts.name
|
||||
if opts.timeout < 0 {
|
||||
return errors.Errorf("negative timeout %d is invalid", opts.timeout)
|
||||
|
||||
@ -13,7 +13,7 @@ type inspectOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -31,7 +31,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
|
||||
@ -31,7 +31,7 @@ func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) {
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
}
|
||||
|
||||
func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options pluginOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "install [OPTIONS] PLUGIN [KEY=VALUE...]",
|
||||
@ -57,12 +57,12 @@ type pluginRegistryService struct {
|
||||
registry.Service
|
||||
}
|
||||
|
||||
func (s pluginRegistryService) ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error) {
|
||||
repoInfo, err := s.Service.ResolveRepository(name)
|
||||
func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
|
||||
repoInfo, err = s.Service.ResolveRepository(name)
|
||||
if repoInfo != nil {
|
||||
repoInfo.Class = "plugin"
|
||||
}
|
||||
return repoInfo, err
|
||||
return
|
||||
}
|
||||
|
||||
func newRegistryService() (registry.Service, error) {
|
||||
@ -73,7 +73,7 @@ func newRegistryService() (registry.Service, error) {
|
||||
return pluginRegistryService{Service: svc}, nil
|
||||
}
|
||||
|
||||
func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
|
||||
func buildPullConfig(ctx context.Context, dockerCli *command.DockerCli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
|
||||
// Names with both tag and digest will be treated by the daemon
|
||||
// as a pull by digest with a local name for the tag
|
||||
// (if no local name is provided).
|
||||
@ -130,7 +130,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func runInstall(dockerCli command.Cli, opts pluginOptions) error {
|
||||
func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
|
||||
var localName string
|
||||
if opts.localName != "" {
|
||||
aref, err := reference.ParseNormalizedNamed(opts.localName)
|
||||
@ -163,7 +163,7 @@ func runInstall(dockerCli command.Cli, opts pluginOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func acceptPrivileges(dockerCli command.Cli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
||||
func acceptPrivileges(dockerCli *command.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
||||
return func(privileges types.PluginPrivileges) (bool, error) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
|
||||
for _, privilege := range privileges {
|
||||
|
||||
@ -16,7 +16,7 @@ type listOptions struct {
|
||||
filter opts.FilterOpt
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
options := listOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,7 +39,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runList(dockerCli command.Cli, options listOptions) error {
|
||||
func runList(dockerCli *command.DockerCli, options listOptions) error {
|
||||
plugins, err := dockerCli.Client().PluginList(context.Background(), options.filter.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -16,7 +16,7 @@ type rmOptions struct {
|
||||
plugins []string
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -35,7 +35,7 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRemove(dockerCli command.Cli, opts *rmOptions) error {
|
||||
func runRemove(dockerCli *command.DockerCli, opts *rmOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs cli.Errors
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newSetCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newSetCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set PLUGIN KEY=VALUE [KEY=VALUE...]",
|
||||
Short: "Change settings for a plugin",
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newUpgradeCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options pluginOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "upgrade [OPTIONS] PLUGIN [REMOTE]",
|
||||
@ -26,7 +26,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
return runUpgrade(dockerCli, options)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.26"},
|
||||
Tags: map[string]string{"version": "1.26"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -35,7 +35,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpgrade(dockerCli command.Cli, opts pluginOptions) error {
|
||||
func runUpgrade(dockerCli *command.DockerCli, opts pluginOptions) error {
|
||||
ctx := context.Background()
|
||||
p, _, err := dockerCli.Client().PluginInspectWithRaw(ctx, opts.localName)
|
||||
if err != nil {
|
||||
|
||||
@ -8,13 +8,14 @@ import (
|
||||
)
|
||||
|
||||
// NewSecretCommand returns a cobra command for `secret` subcommands
|
||||
func NewSecretCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewSecretCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "secret",
|
||||
Short: "Manage Docker secrets",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Use: "secret",
|
||||
Short: "Manage Docker secrets",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newSecretListCommand(dockerCli),
|
||||
|
||||
@ -14,7 +14,6 @@ type fakeClient struct {
|
||||
serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error)
|
||||
serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
|
||||
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
|
||||
taskListFunc func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
|
||||
infoFunc func(ctx context.Context) (types.Info, error)
|
||||
}
|
||||
|
||||
@ -23,9 +22,6 @@ func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions
|
||||
}
|
||||
|
||||
func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
if f.taskListFunc != nil {
|
||||
return f.taskListFunc(ctx, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@ -8,13 +8,14 @@ import (
|
||||
)
|
||||
|
||||
// NewServiceCommand returns a cobra command for `service` subcommands
|
||||
func NewServiceCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "service",
|
||||
Short: "Manage services",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.24"},
|
||||
Use: "service",
|
||||
Short: "Manage services",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.24"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newCreateCommand(dockerCli),
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/spf13/cobra"
|
||||
@ -59,9 +58,6 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.Var(&opts.hosts, flagHost, "Set one or more custom host-to-IP mappings (host:ip)")
|
||||
flags.SetAnnotation(flagHost, "version", []string{"1.25"})
|
||||
|
||||
flags.Var(cliopts.NewListOptsRef(&opts.resources.resGenericResources, ValidateSingleGenericResource), "generic-resource", "User defined resources")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
|
||||
flags.SetInterspersed(false)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
swarmapi "github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/api/genericresource"
|
||||
)
|
||||
|
||||
// GenericResource is a concept that a user can use to advertise user-defined
|
||||
// resources on a node and thus better place services based on these resources.
|
||||
// E.g: NVIDIA GPUs, Intel FPGAs, ...
|
||||
// See https://github.com/docker/swarmkit/blob/master/design/generic_resources.md
|
||||
|
||||
// ValidateSingleGenericResource validates that a single entry in the
|
||||
// generic resource list is valid.
|
||||
// i.e 'GPU=UID1' is valid however 'GPU:UID1' or 'UID1' isn't
|
||||
func ValidateSingleGenericResource(val string) (string, error) {
|
||||
if strings.Count(val, "=") < 1 {
|
||||
return "", fmt.Errorf("invalid generic-resource format `%s` expected `name=value`", val)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// ParseGenericResources parses an array of Generic resourceResources
|
||||
// Requesting Named Generic Resources for a service is not supported this
|
||||
// is filtered here.
|
||||
func ParseGenericResources(value []string) ([]swarm.GenericResource, error) {
|
||||
if len(value) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resources, err := genericresource.Parse(value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid generic resource specification")
|
||||
}
|
||||
|
||||
swarmResources := genericResourcesFromGRPC(resources)
|
||||
for _, res := range swarmResources {
|
||||
if res.NamedResourceSpec != nil {
|
||||
return nil, fmt.Errorf("invalid generic-resource request `%s=%s`, Named Generic Resources is not supported for service create or update", res.NamedResourceSpec.Kind, res.NamedResourceSpec.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return swarmResources, nil
|
||||
}
|
||||
|
||||
// genericResourcesFromGRPC converts a GRPC GenericResource to a GenericResource
|
||||
func genericResourcesFromGRPC(genericRes []*swarmapi.GenericResource) []swarm.GenericResource {
|
||||
var generic []swarm.GenericResource
|
||||
for _, res := range genericRes {
|
||||
var current swarm.GenericResource
|
||||
|
||||
switch r := res.Resource.(type) {
|
||||
case *swarmapi.GenericResource_DiscreteResourceSpec:
|
||||
current.DiscreteResourceSpec = &swarm.DiscreteGenericResource{
|
||||
Kind: r.DiscreteResourceSpec.Kind,
|
||||
Value: r.DiscreteResourceSpec.Value,
|
||||
}
|
||||
case *swarmapi.GenericResource_NamedResourceSpec:
|
||||
current.NamedResourceSpec = &swarm.NamedGenericResource{
|
||||
Kind: r.NamedResourceSpec.Kind,
|
||||
Value: r.NamedResourceSpec.Value,
|
||||
}
|
||||
}
|
||||
|
||||
generic = append(generic, current)
|
||||
}
|
||||
|
||||
return generic
|
||||
}
|
||||
|
||||
func buildGenericResourceMap(genericRes []swarm.GenericResource) (map[string]swarm.GenericResource, error) {
|
||||
m := make(map[string]swarm.GenericResource)
|
||||
|
||||
for _, res := range genericRes {
|
||||
if res.DiscreteResourceSpec == nil {
|
||||
return nil, fmt.Errorf("invalid generic-resource `%+v` for service task", res)
|
||||
}
|
||||
|
||||
_, ok := m[res.DiscreteResourceSpec.Kind]
|
||||
if ok {
|
||||
return nil, fmt.Errorf("duplicate generic-resource `%+v` for service task", res.DiscreteResourceSpec.Kind)
|
||||
}
|
||||
|
||||
m[res.DiscreteResourceSpec.Kind] = res
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func buildGenericResourceList(genericRes map[string]swarm.GenericResource) []swarm.GenericResource {
|
||||
var l []swarm.GenericResource
|
||||
|
||||
for _, res := range genericRes {
|
||||
l = append(l, res)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateSingleGenericResource(t *testing.T) {
|
||||
incorrect := []string{"foo", "fooo-bar"}
|
||||
correct := []string{"foo=bar", "bar=1", "foo=barbar"}
|
||||
|
||||
for _, v := range incorrect {
|
||||
_, err := ValidateSingleGenericResource(v)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
for _, v := range correct {
|
||||
_, err := ValidateSingleGenericResource(v)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
@ -54,7 +54,7 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
// Service inspect shows defaults values in empty fields.
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref, types.ServiceInspectOptions{InsertDefaults: true})
|
||||
if err == nil || !apiclient.IsErrNotFound(err) {
|
||||
if err == nil || !apiclient.IsErrServiceNotFound(err) {
|
||||
return service, nil, err
|
||||
}
|
||||
return nil, nil, errors.Errorf("Error: no such service: %s", ref)
|
||||
@ -62,7 +62,7 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
|
||||
getNetwork := func(ref string) (interface{}, []byte, error) {
|
||||
network, _, err := client.NetworkInspectWithRaw(ctx, ref, types.NetworkInspectOptions{Scope: "swarm"})
|
||||
if err == nil || !apiclient.IsErrNotFound(err) {
|
||||
if err == nil || !apiclient.IsErrNetworkNotFound(err) {
|
||||
return network, nil, err
|
||||
}
|
||||
return nil, nil, errors.Errorf("Error: no such network: %s", ref)
|
||||
|
||||
@ -37,7 +37,7 @@ type logsOptions struct {
|
||||
target string
|
||||
}
|
||||
|
||||
func newLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts logsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -48,7 +48,7 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts.target = args[0]
|
||||
return runLogs(dockerCli, &opts)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.29"},
|
||||
Tags: map[string]string{"version": "1.29"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -68,7 +68,7 @@ func newLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
@ -97,12 +97,12 @@ func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
service, _, err := cli.ServiceInspectWithRaw(ctx, opts.target, types.ServiceInspectOptions{})
|
||||
if err != nil {
|
||||
// if it's any error other than service not found, it's Real
|
||||
if !client.IsErrNotFound(err) {
|
||||
if !client.IsErrServiceNotFound(err) {
|
||||
return err
|
||||
}
|
||||
task, _, err := cli.TaskInspectWithRaw(ctx, opts.target)
|
||||
if err != nil {
|
||||
if client.IsErrNotFound(err) {
|
||||
if client.IsErrTaskNotFound(err) {
|
||||
// if the task isn't found, rewrite the error to be clear
|
||||
// that we looked for services AND tasks and found none
|
||||
err = fmt.Errorf("no such task or service: %v", opts.target)
|
||||
|
||||
@ -222,30 +222,23 @@ func (opts updateOptions) rollbackConfig(flags *pflag.FlagSet) *swarm.UpdateConf
|
||||
}
|
||||
|
||||
type resourceOptions struct {
|
||||
limitCPU opts.NanoCPUs
|
||||
limitMemBytes opts.MemBytes
|
||||
resCPU opts.NanoCPUs
|
||||
resMemBytes opts.MemBytes
|
||||
resGenericResources []string
|
||||
limitCPU opts.NanoCPUs
|
||||
limitMemBytes opts.MemBytes
|
||||
resCPU opts.NanoCPUs
|
||||
resMemBytes opts.MemBytes
|
||||
}
|
||||
|
||||
func (r *resourceOptions) ToResourceRequirements() (*swarm.ResourceRequirements, error) {
|
||||
generic, err := ParseGenericResources(r.resGenericResources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
|
||||
return &swarm.ResourceRequirements{
|
||||
Limits: &swarm.Resources{
|
||||
NanoCPUs: r.limitCPU.Value(),
|
||||
MemoryBytes: r.limitMemBytes.Value(),
|
||||
},
|
||||
Reservations: &swarm.Resources{
|
||||
NanoCPUs: r.resCPU.Value(),
|
||||
MemoryBytes: r.resMemBytes.Value(),
|
||||
GenericResources: generic,
|
||||
NanoCPUs: r.resCPU.Value(),
|
||||
MemoryBytes: r.resMemBytes.Value(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
type restartPolicyOptions struct {
|
||||
@ -361,7 +354,7 @@ func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, net
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
netAttach = append(netAttach, swarm.NetworkAttachmentConfig{
|
||||
netAttach = append(netAttach, swarm.NetworkAttachmentConfig{ // nolint: gosimple
|
||||
Target: net.Target,
|
||||
Aliases: net.Aliases,
|
||||
DriverOpts: net.DriverOpts,
|
||||
@ -512,8 +505,6 @@ type serviceOptions struct {
|
||||
healthcheck healthCheckOptions
|
||||
secrets opts.SecretOpt
|
||||
configs opts.ConfigOpt
|
||||
|
||||
isolation string
|
||||
}
|
||||
|
||||
func newServiceOptions() *serviceOptions {
|
||||
@ -595,11 +586,6 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
return service, err
|
||||
}
|
||||
|
||||
resources, err := options.resources.ToResourceRequirements()
|
||||
if err != nil {
|
||||
return service, err
|
||||
}
|
||||
|
||||
service = swarm.ServiceSpec{
|
||||
Annotations: swarm.Annotations{
|
||||
Name: options.name,
|
||||
@ -628,10 +614,9 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
|
||||
Hosts: convertExtraHostsToSwarmHosts(options.hosts.GetAll()),
|
||||
StopGracePeriod: options.ToStopGracePeriod(flags),
|
||||
Healthcheck: healthConfig,
|
||||
Isolation: container.Isolation(options.isolation),
|
||||
},
|
||||
Networks: networks,
|
||||
Resources: resources,
|
||||
Resources: options.resources.ToResourceRequirements(),
|
||||
RestartPolicy: options.restartPolicy.ToRestartPolicy(flags),
|
||||
Placement: &swarm.Placement{
|
||||
Constraints: options.constraints.GetAll(),
|
||||
@ -799,8 +784,6 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
|
||||
|
||||
flags.StringVar(&opts.stopSignal, flagStopSignal, "", "Signal to stop the container")
|
||||
flags.SetAnnotation(flagStopSignal, "version", []string{"1.28"})
|
||||
flags.StringVar(&opts.isolation, flagIsolation, "", "Service container isolation mode")
|
||||
flags.SetAnnotation(flagIsolation, "version", []string{"1.35"})
|
||||
}
|
||||
|
||||
const (
|
||||
@ -830,8 +813,6 @@ const (
|
||||
flagEnvFile = "env-file"
|
||||
flagEnvRemove = "env-rm"
|
||||
flagEnvAdd = "env-add"
|
||||
flagGenericResourcesRemove = "generic-resource-rm"
|
||||
flagGenericResourcesAdd = "generic-resource-add"
|
||||
flagGroup = "group"
|
||||
flagGroupAdd = "group-add"
|
||||
flagGroupRemove = "group-rm"
|
||||
@ -898,5 +879,4 @@ const (
|
||||
flagConfig = "config"
|
||||
flagConfigAdd = "config-add"
|
||||
flagConfigRemove = "config-rm"
|
||||
flagIsolation = "isolation"
|
||||
)
|
||||
|
||||
@ -85,41 +85,3 @@ func TestHealthCheckOptionsToHealthConfigConflict(t *testing.T) {
|
||||
_, err := opt.toHealthConfig()
|
||||
assert.EqualError(t, err, "--no-healthcheck conflicts with --health-* options")
|
||||
}
|
||||
|
||||
func TestResourceOptionsToResourceRequirements(t *testing.T) {
|
||||
incorrectOptions := []resourceOptions{
|
||||
{
|
||||
resGenericResources: []string{"foo=bar", "foo=1"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=bar", "foo=baz"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=bar"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=1", "foo=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range incorrectOptions {
|
||||
_, err := opt.ToResourceRequirements()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
correctOptions := []resourceOptions{
|
||||
{
|
||||
resGenericResources: []string{"foo=1"},
|
||||
},
|
||||
{
|
||||
resGenericResources: []string{"foo=1", "bar=2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, opt := range correctOptions {
|
||||
r, err := opt.ToResourceRequirements()
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, r.Reservations.GenericResources, len(opt.resGenericResources))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -69,9 +69,6 @@ func runPS(dockerCli command.Cli, options psOptions) error {
|
||||
if len(format) == 0 {
|
||||
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
|
||||
}
|
||||
if options.quiet {
|
||||
options.noTrunc = true
|
||||
}
|
||||
if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -90,22 +90,6 @@ func TestRunPSWarnsOnNotFound(t *testing.T) {
|
||||
assert.EqualError(t, err, "no such service: bar")
|
||||
}
|
||||
|
||||
func TestRunPSQuiet(t *testing.T) {
|
||||
client := &fakeClient{
|
||||
serviceListFunc: func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
|
||||
return []swarm.Service{{ID: "foo"}}, nil
|
||||
},
|
||||
taskListFunc: func(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
|
||||
return []swarm.Task{{ID: "sxabyp0obqokwekpun4rjo0b3"}}, nil
|
||||
},
|
||||
}
|
||||
|
||||
cli := test.NewFakeCli(client)
|
||||
err := runPS(cli, psOptions{services: []string{"foo"}, quiet: true, filter: opts.NewFilterOpt()})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "sxabyp0obqokwekpun4rjo0b3\n", cli.OutBuffer().String())
|
||||
}
|
||||
|
||||
func TestUpdateNodeFilter(t *testing.T) {
|
||||
selfNodeID := "foofoo"
|
||||
filter := filters.NewArgs()
|
||||
|
||||
@ -21,7 +21,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runRollback(dockerCli, options, args[0])
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.31"},
|
||||
Tags: map[string]string{"version": "1.31"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -8,10 +8,10 @@ import (
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
||||
@ -92,15 +92,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
|
||||
flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
|
||||
flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
|
||||
flags.Var(&options.hosts, flagHostAdd, "Add a custom host-to-IP mapping (host:ip)")
|
||||
flags.Var(&options.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
|
||||
|
||||
// Add needs parsing, Remove only needs the key
|
||||
flags.Var(newListOptsVar(), flagGenericResourcesRemove, "Remove a Generic resource")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
flags.Var(newListOptsVarWithValidator(ValidateSingleGenericResource), flagGenericResourcesAdd, "Add a Generic resource")
|
||||
flags.SetAnnotation(flagHostAdd, "version", []string{"1.32"})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -108,10 +102,6 @@ func newListOptsVar() *opts.ListOpts {
|
||||
return opts.NewListOptsRef(&[]string{}, nil)
|
||||
}
|
||||
|
||||
func newListOptsVarWithValidator(validator opts.ValidatorFctType) *opts.ListOpts {
|
||||
return opts.NewListOptsRef(&[]string{}, validator)
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
|
||||
apiClient := dockerCli.Client()
|
||||
@ -279,14 +269,6 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
||||
}
|
||||
}
|
||||
|
||||
updateIsolation := func(flag string, field *container.Isolation) error {
|
||||
if flags.Changed(flag) {
|
||||
val, _ := flags.GetString(flag)
|
||||
*field = container.Isolation(val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
cspec := spec.TaskTemplate.ContainerSpec
|
||||
task := &spec.TaskTemplate
|
||||
|
||||
@ -306,9 +288,6 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
||||
updateString(flagWorkdir, &cspec.Dir)
|
||||
updateString(flagUser, &cspec.User)
|
||||
updateString(flagHostname, &cspec.Hostname)
|
||||
if err := updateIsolation(flagIsolation, &cspec.Isolation); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := updateMounts(flags, &cspec.Mounts); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -324,14 +303,6 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
||||
updateInt64Value(flagReserveMemory, &task.Resources.Reservations.MemoryBytes)
|
||||
}
|
||||
|
||||
if err := addGenericResources(flags, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := removeGenericResources(flags, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateDurationOpt(flagStopGracePeriod, &cspec.StopGracePeriod)
|
||||
|
||||
if anyChanged(flags, flagRestartCondition, flagRestartDelay, flagRestartMaxAttempts, flagRestartWindow) {
|
||||
@ -488,72 +459,6 @@ func anyChanged(flags *pflag.FlagSet, fields ...string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func addGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
|
||||
if !flags.Changed(flagGenericResourcesAdd) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if spec.Resources == nil {
|
||||
spec.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
|
||||
if spec.Resources.Reservations == nil {
|
||||
spec.Resources.Reservations = &swarm.Resources{}
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagGenericResourcesAdd).Value.(*opts.ListOpts).GetAll()
|
||||
generic, err := ParseGenericResources(values)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, toAddRes := range generic {
|
||||
m[toAddRes.DiscreteResourceSpec.Kind] = toAddRes
|
||||
}
|
||||
|
||||
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeGenericResources(flags *pflag.FlagSet, spec *swarm.TaskSpec) error {
|
||||
// Can only be Discrete Resources
|
||||
if !flags.Changed(flagGenericResourcesRemove) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if spec.Resources == nil {
|
||||
spec.Resources = &swarm.ResourceRequirements{}
|
||||
}
|
||||
|
||||
if spec.Resources.Reservations == nil {
|
||||
spec.Resources.Reservations = &swarm.Resources{}
|
||||
}
|
||||
|
||||
values := flags.Lookup(flagGenericResourcesRemove).Value.(*opts.ListOpts).GetAll()
|
||||
|
||||
m, err := buildGenericResourceMap(spec.Resources.Reservations.GenericResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, toRemoveRes := range values {
|
||||
if _, ok := m[toRemoveRes]; !ok {
|
||||
return fmt.Errorf("could not find generic-resource `%s` to remove it", toRemoveRes)
|
||||
}
|
||||
|
||||
delete(m, toRemoveRes)
|
||||
}
|
||||
|
||||
spec.Resources.Reservations.GenericResources = buildGenericResourceList(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePlacementConstraints(flags *pflag.FlagSet, placement *swarm.Placement) {
|
||||
if flags.Changed(flagConstraintAdd) {
|
||||
values := flags.Lookup(flagConstraintAdd).Value.(*opts.ListOpts).GetAll()
|
||||
@ -963,10 +868,6 @@ func updateReplicas(flags *pflag.FlagSet, serviceMode *swarm.ServiceMode) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateHosts performs a diff between existing host entries, entries to be
|
||||
// removed, and entries to be added. Host entries preserve the order in which they
|
||||
// were added, as the specification mentions that in case multiple entries for a
|
||||
// host exist, the first entry should be used (by default).
|
||||
func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
|
||||
// Combine existing Hosts (in swarmkit format) with the host to add (convert to swarmkit format)
|
||||
if flags.Changed(flagHostAdd) {
|
||||
@ -1001,6 +902,9 @@ func updateHosts(flags *pflag.FlagSet, hosts *[]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Sort so that result is predictable.
|
||||
sort.Strings(newHosts)
|
||||
|
||||
*hosts = newHosts
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
mounttypes "github.com/docker/docker/api/types/mount"
|
||||
@ -166,7 +165,7 @@ func TestUpdateDNSConfig(t *testing.T) {
|
||||
// IPv6
|
||||
flags.Set("dns-add", "2001:db8:abc8::1")
|
||||
// Invalid dns record
|
||||
testutil.ErrorContains(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
|
||||
assert.EqualError(t, flags.Set("dns-add", "x.y.z.w"), "x.y.z.w is not an ip address")
|
||||
|
||||
// domains with duplicates
|
||||
flags.Set("dns-search-add", "example.com")
|
||||
@ -174,7 +173,7 @@ func TestUpdateDNSConfig(t *testing.T) {
|
||||
flags.Set("dns-search-add", "example.org")
|
||||
flags.Set("dns-search-rm", "example.org")
|
||||
// Invalid dns search domain
|
||||
testutil.ErrorContains(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
|
||||
assert.EqualError(t, flags.Set("dns-search-add", "example$com"), "example$com is not a valid domain")
|
||||
|
||||
flags.Set("dns-option-add", "ndots:9")
|
||||
flags.Set("dns-option-rm", "timeout:3")
|
||||
@ -363,26 +362,15 @@ func TestUpdateHosts(t *testing.T) {
|
||||
// just hostname should work as well
|
||||
flags.Set("host-rm", "example.net")
|
||||
// bad format error
|
||||
testutil.ErrorContains(t, flags.Set("host-add", "$example.com$"), `bad format for add-host: "$example.com$"`)
|
||||
assert.EqualError(t, flags.Set("host-add", "$example.com$"), `bad format for add-host: "$example.com$"`)
|
||||
|
||||
hosts := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 example.net"}
|
||||
expected := []string{"1.2.3.4 example.com", "4.3.2.1 example.org", "2001:db8:abc8::1 ipv6.net"}
|
||||
|
||||
err := updateHosts(flags, &hosts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, hosts)
|
||||
}
|
||||
|
||||
func TestUpdateHostsPreservesOrder(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
flags.Set("host-add", "foobar:127.0.0.2")
|
||||
flags.Set("host-add", "foobar:127.0.0.1")
|
||||
flags.Set("host-add", "foobar:127.0.0.3")
|
||||
|
||||
hosts := []string{}
|
||||
err := updateHosts(flags, &hosts)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"127.0.0.2 foobar", "127.0.0.1 foobar", "127.0.0.3 foobar"}, hosts)
|
||||
updateHosts(flags, &hosts)
|
||||
require.Len(t, hosts, 3)
|
||||
assert.Equal(t, "1.2.3.4 example.com", hosts[0])
|
||||
assert.Equal(t, "2001:db8:abc8::1 ipv6.net", hosts[1])
|
||||
assert.Equal(t, "4.3.2.1 example.org", hosts[2])
|
||||
}
|
||||
|
||||
func TestUpdatePortsRmWithProtocol(t *testing.T) {
|
||||
@ -518,71 +506,3 @@ func TestUpdateStopSignal(t *testing.T) {
|
||||
updateService(nil, nil, flags, spec)
|
||||
assert.Equal(t, "SIGWINCH", cspec.StopSignal)
|
||||
}
|
||||
|
||||
func TestUpdateIsolationValid(t *testing.T) {
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
err := flags.Set("isolation", "process")
|
||||
require.NoError(t, err)
|
||||
spec := swarm.ServiceSpec{
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{},
|
||||
},
|
||||
}
|
||||
err = updateService(context.Background(), nil, flags, &spec)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, container.IsolationProcess, spec.TaskTemplate.ContainerSpec.Isolation)
|
||||
}
|
||||
|
||||
func TestUpdateIsolationInvalid(t *testing.T) {
|
||||
// validation depends on daemon os / version so validation should be done on the daemon side
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
err := flags.Set("isolation", "test")
|
||||
require.NoError(t, err)
|
||||
spec := swarm.ServiceSpec{
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
ContainerSpec: &swarm.ContainerSpec{},
|
||||
},
|
||||
}
|
||||
err = updateService(context.Background(), nil, flags, &spec)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, container.Isolation("test"), spec.TaskTemplate.ContainerSpec.Isolation)
|
||||
}
|
||||
|
||||
func TestAddGenericResources(t *testing.T) {
|
||||
task := &swarm.TaskSpec{}
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
|
||||
assert.Nil(t, addGenericResources(flags, task))
|
||||
|
||||
flags.Set(flagGenericResourcesAdd, "foo=1")
|
||||
assert.NoError(t, addGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
|
||||
|
||||
// Checks that foo isn't added a 2nd time
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "bar=1")
|
||||
assert.NoError(t, addGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 2)
|
||||
}
|
||||
|
||||
func TestRemoveGenericResources(t *testing.T) {
|
||||
task := &swarm.TaskSpec{}
|
||||
flags := newUpdateCommand(nil).Flags()
|
||||
|
||||
assert.Nil(t, removeGenericResources(flags, task))
|
||||
|
||||
flags.Set(flagGenericResourcesRemove, "foo")
|
||||
assert.Error(t, removeGenericResources(flags, task))
|
||||
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "foo=1")
|
||||
addGenericResources(flags, task)
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesAdd, "bar=1")
|
||||
addGenericResources(flags, task)
|
||||
|
||||
flags = newUpdateCommand(nil).Flags()
|
||||
flags.Set(flagGenericResourcesRemove, "foo")
|
||||
assert.NoError(t, removeGenericResources(flags, task))
|
||||
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
|
||||
}
|
||||
|
||||
@ -7,13 +7,14 @@ import (
|
||||
)
|
||||
|
||||
// NewStackCommand returns a cobra command for `stack` subcommands
|
||||
func NewStackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "stack",
|
||||
Short: "Manage Docker stacks",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Use: "stack",
|
||||
Short: "Manage Docker stacks",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newDeployCommand(dockerCli),
|
||||
@ -30,6 +31,6 @@ func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := newDeployCommand(dockerCli)
|
||||
// Remove the aliases at the top level
|
||||
cmd.Aliases = []string{}
|
||||
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
|
||||
cmd.Tags = map[string]string{"experimental": "", "version": "1.25"}
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -222,9 +222,8 @@ func createSecrets(
|
||||
if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
|
||||
}
|
||||
case apiclient.IsErrNotFound(err):
|
||||
case apiclient.IsErrSecretNotFound(err):
|
||||
// secret does not exist, then we create a new one.
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
|
||||
if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
|
||||
}
|
||||
@ -250,9 +249,8 @@ func createConfigs(
|
||||
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
|
||||
errors.Wrapf(err, "failed to update config %s", configSpec.Name)
|
||||
}
|
||||
case apiclient.IsErrNotFound(err):
|
||||
case apiclient.IsErrConfigNotFound(err):
|
||||
// config does not exist, then we create a new one.
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
|
||||
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
|
||||
errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ func newCACommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCA(dockerCli, cmd.Flags(), opts)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.30"},
|
||||
Tags: map[string]string{"version": "1.30"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -8,13 +8,14 @@ import (
|
||||
)
|
||||
|
||||
// NewSwarmCommand returns a cobra command for `swarm` subcommands
|
||||
// nolint: interfacer
|
||||
func NewSwarmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "swarm",
|
||||
Short: "Manage Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"version": "1.24"},
|
||||
Use: "swarm",
|
||||
Short: "Manage Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"version": "1.24"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newInitCommand(dockerCli),
|
||||
|
||||
@ -8,7 +8,6 @@ Flags:
|
||||
--cert-expiry duration Validity period for node certificates (ns|us|ms|s|m|h) (default 2160h0m0s)
|
||||
--dispatcher-heartbeat duration Dispatcher heartbeat period (ns|us|ms|s|m|h) (default 5s)
|
||||
--external-ca external-ca Specifications of one or more certificate signing endpoints
|
||||
-h, --help help for update
|
||||
--max-snapshots uint Number of additional Raft snapshots to retain
|
||||
--snapshot-interval uint Number of log entries between Raft snapshots (default 10000)
|
||||
--task-history-limit int Task history retention limit (default 5)
|
||||
|
||||
@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
// NewSystemCommand returns a cobra command for `system` subcommands
|
||||
func NewSystemCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "system",
|
||||
Short: "Manage Docker",
|
||||
|
||||
@ -16,7 +16,7 @@ type diskUsageOptions struct {
|
||||
}
|
||||
|
||||
// newDiskUsageCommand creates a new cobra.Command for `docker df`
|
||||
func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts diskUsageOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -26,7 +26,7 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runDiskUsage(dockerCli, opts)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -37,7 +37,7 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
|
||||
func runDiskUsage(dockerCli *command.DockerCli, opts diskUsageOptions) error {
|
||||
if opts.verbose && len(opts.format) != 0 {
|
||||
return errors.New("the verbose and the format options conflict")
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ type eventsOptions struct {
|
||||
}
|
||||
|
||||
// NewEventsCommand creates a new cobra.Command for `docker events`
|
||||
func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
options := eventsOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -48,7 +48,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEvents(dockerCli command.Cli, options *eventsOptions) error {
|
||||
func runEvents(dockerCli *command.DockerCli, options *eventsOptions) error {
|
||||
tmpl, err := makeTemplate(options.format)
|
||||
if err != nil {
|
||||
return cli.StatusError{
|
||||
|
||||
@ -22,7 +22,7 @@ type infoOptions struct {
|
||||
}
|
||||
|
||||
// NewInfoCommand creates a new cobra.Command for `docker info`
|
||||
func NewInfoCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewInfoCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts infoOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -41,7 +41,7 @@ func NewInfoCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInfo(dockerCli command.Cli, opts *infoOptions) error {
|
||||
func runInfo(dockerCli *command.DockerCli, opts *infoOptions) error {
|
||||
ctx := context.Background()
|
||||
info, err := dockerCli.Client().Info(ctx)
|
||||
if err != nil {
|
||||
@ -357,7 +357,7 @@ func getBackingFs(info types.Info) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatInfo(dockerCli command.Cli, info types.Info, format string) error {
|
||||
func formatInfo(dockerCli *command.DockerCli, info types.Info, format string) error {
|
||||
tmpl, err := templates.Parse(format)
|
||||
if err != nil {
|
||||
return cli.StatusError{StatusCode: 64,
|
||||
|
||||
@ -37,7 +37,7 @@ func newPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runPrune(dockerCli, options)
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
@ -1,52 +1,34 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/templates"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var versionTemplate = `{{with .Client -}}
|
||||
Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
|
||||
Version: {{.Version}}
|
||||
API version: {{.APIVersion}}{{if ne .APIVersion .DefaultAPIVersion}} (downgraded from {{.DefaultAPIVersion}}){{end}}
|
||||
Go version: {{.GoVersion}}
|
||||
Git commit: {{.GitCommit}}
|
||||
Built: {{.BuildTime}}
|
||||
OS/Arch: {{.Os}}/{{.Arch}}
|
||||
{{- end}}
|
||||
var versionTemplate = `Client:
|
||||
Version: {{.Client.Version}}
|
||||
API version: {{.Client.APIVersion}}{{if ne .Client.APIVersion .Client.DefaultAPIVersion}} (downgraded from {{.Client.DefaultAPIVersion}}){{end}}
|
||||
Go version: {{.Client.GoVersion}}
|
||||
Git commit: {{.Client.GitCommit}}
|
||||
Built: {{.Client.BuildTime}}
|
||||
OS/Arch: {{.Client.Os}}/{{.Client.Arch}}{{if .ServerOK}}
|
||||
|
||||
{{- if .ServerOK}}{{with .Server}}
|
||||
|
||||
Server:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
|
||||
{{- range $component := .Components}}
|
||||
{{$component.Name}}:
|
||||
{{- if eq $component.Name "Engine" }}
|
||||
Version: {{.Version}}
|
||||
API version: {{index .Details "ApiVersion"}} (minimum version {{index .Details "MinAPIVersion"}})
|
||||
Go version: {{index .Details "GoVersion"}}
|
||||
Git commit: {{index .Details "GitCommit"}}
|
||||
Built: {{index .Details "BuildTime"}}
|
||||
OS/Arch: {{index .Details "Os"}}/{{index .Details "Arch"}}
|
||||
Experimental: {{index .Details "Experimental"}}
|
||||
{{- else }}
|
||||
Version: {{$component.Version}}
|
||||
{{- $detailsOrder := getDetailsOrder $component}}
|
||||
{{- range $key := $detailsOrder}}
|
||||
{{$key}}: {{index $component.Details $key}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end}}{{end}}`
|
||||
Server:
|
||||
Version: {{.Server.Version}}
|
||||
API version: {{.Server.APIVersion}} (minimum version {{.Server.MinAPIVersion}})
|
||||
Go version: {{.Server.GoVersion}}
|
||||
Git commit: {{.Server.GitCommit}}
|
||||
Built: {{.Server.BuildTime}}
|
||||
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}
|
||||
Experimental: {{.Server.Experimental}}{{end}}`
|
||||
|
||||
type versionOptions struct {
|
||||
format string
|
||||
@ -59,8 +41,6 @@ type versionInfo struct {
|
||||
}
|
||||
|
||||
type clientVersion struct {
|
||||
Platform struct{ Name string } `json:",omitempty"`
|
||||
|
||||
Version string
|
||||
APIVersion string `json:"ApiVersion"`
|
||||
DefaultAPIVersion string `json:"DefaultAPIVersion,omitempty"`
|
||||
@ -97,27 +77,15 @@ func NewVersionCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func reformatDate(buildTime string) string {
|
||||
t, errTime := time.Parse(time.RFC3339Nano, buildTime)
|
||||
if errTime == nil {
|
||||
return t.Format(time.ANSIC)
|
||||
}
|
||||
return buildTime
|
||||
}
|
||||
|
||||
func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
templateFormat := versionTemplate
|
||||
tmpl := templates.New("version")
|
||||
if opts.format != "" {
|
||||
templateFormat = opts.format
|
||||
} else {
|
||||
tmpl = tmpl.Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder})
|
||||
}
|
||||
|
||||
var err error
|
||||
tmpl, err = tmpl.Parse(templateFormat)
|
||||
tmpl, err := templates.Parse(templateFormat)
|
||||
if err != nil {
|
||||
return cli.StatusError{StatusCode: 64,
|
||||
Status: "Template parsing error: " + err.Error()}
|
||||
@ -135,41 +103,22 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
||||
Arch: runtime.GOARCH,
|
||||
},
|
||||
}
|
||||
vd.Client.Platform.Name = cli.PlatformName
|
||||
|
||||
serverVersion, err := dockerCli.Client().ServerVersion(ctx)
|
||||
if err == nil {
|
||||
vd.Server = &serverVersion
|
||||
}
|
||||
|
||||
// first we need to make BuildTime more human friendly
|
||||
vd.Client.BuildTime = reformatDate(vd.Client.BuildTime)
|
||||
t, errTime := time.Parse(time.RFC3339Nano, vd.Client.BuildTime)
|
||||
if errTime == nil {
|
||||
vd.Client.BuildTime = t.Format(time.ANSIC)
|
||||
}
|
||||
|
||||
sv, err := dockerCli.Client().ServerVersion(ctx)
|
||||
if err == nil {
|
||||
vd.Server = &sv
|
||||
foundEngine := false
|
||||
for _, component := range sv.Components {
|
||||
if component.Name == "Engine" {
|
||||
foundEngine = true
|
||||
buildTime, ok := component.Details["BuildTime"]
|
||||
if ok {
|
||||
component.Details["BuildTime"] = reformatDate(buildTime)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundEngine {
|
||||
vd.Server.Components = append(vd.Server.Components, types.ComponentVersion{
|
||||
Name: "Engine",
|
||||
Version: sv.Version,
|
||||
Details: map[string]string{
|
||||
"ApiVersion": sv.APIVersion,
|
||||
"MinAPIVersion": sv.MinAPIVersion,
|
||||
"GitCommit": sv.GitCommit,
|
||||
"GoVersion": sv.GoVersion,
|
||||
"Os": sv.Os,
|
||||
"Arch": sv.Arch,
|
||||
"BuildTime": reformatDate(vd.Server.BuildTime),
|
||||
"Experimental": fmt.Sprintf("%t", sv.Experimental),
|
||||
},
|
||||
})
|
||||
if vd.ServerOK() {
|
||||
t, errTime = time.Parse(time.RFC3339Nano, vd.Server.BuildTime)
|
||||
if errTime == nil {
|
||||
vd.Server.BuildTime = t.Format(time.ANSIC)
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,12 +128,3 @@ func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
||||
dockerCli.Out().Write([]byte{'\n'})
|
||||
return err
|
||||
}
|
||||
|
||||
func getDetailsOrder(v types.ComponentVersion) []string {
|
||||
out := make([]string, 0, len(v.Details))
|
||||
for k := range v.Details {
|
||||
out = append(out, k)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user