mirror of https://github.com/docker/cli.git
Compare commits
79 Commits
68cb03338a
...
06f88f4dd1
Author | SHA1 | Date |
---|---|---|
Sebastiaan van Stijn | 06f88f4dd1 | |
Sebastiaan van Stijn | 4adbb18f1c | |
Sebastiaan van Stijn | e00ed82399 | |
Sebastiaan van Stijn | 3dd7621240 | |
Sebastiaan van Stijn | 4242cda826 | |
Sebastiaan van Stijn | a4228409d2 | |
Sebastiaan van Stijn | 7c80e4f938 | |
Harald Albers | 06260e68f3 | |
Harald Albers | 4525fe37b4 | |
Harald Albers | db0ed1e216 | |
Harald Albers | 2915749279 | |
Harald Albers | 3a2503fa43 | |
Harald Albers | 9a9ae231a9 | |
Harald Albers | 5f7c43e5e6 | |
Harald Albers | 3292afe6e6 | |
Harald Albers | 5d709a8d9f | |
Harald Albers | 2d89339b34 | |
Harald Albers | ac7bde6f64 | |
Harald Albers | e513454244 | |
Harald Albers | c555327f0b | |
Harald Albers | b598ec8cdb | |
Harald Albers | 761d76750c | |
Sebastiaan van Stijn | 917d2dc837 | |
Sebastiaan van Stijn | f9497b8a46 | |
Paweł Gronowski | 382d4c34a9 | |
aevesdocker | 1440f9f8cf | |
Sebastiaan van Stijn | 9c01d924fb | |
Sebastiaan van Stijn | 8c22315e31 | |
Laura Brehm | 13754f6776 | |
Sebastiaan van Stijn | 9eb7b52189 | |
David Karlsson | 172f340112 | |
Sebastiaan van Stijn | 750b8ebcdc | |
Sebastiaan van Stijn | 4a7b04d412 | |
Sebastiaan van Stijn | d77760fe53 | |
Sebastiaan van Stijn | 32b40deb46 | |
Sebastiaan van Stijn | 40833fd296 | |
Sebastiaan van Stijn | 78a7e15032 | |
Sebastiaan van Stijn | 4a71ce02e6 | |
Sebastiaan van Stijn | 7d9ea25564 | |
Sebastiaan van Stijn | 046ac9714c | |
Sebastiaan van Stijn | 762b5a8df3 | |
Sebastiaan van Stijn | 417974cdc3 | |
Sebastiaan van Stijn | bf37e26b33 | |
Sebastiaan van Stijn | 6489a777e5 | |
Sebastiaan van Stijn | 20de861134 | |
Sebastiaan van Stijn | 1448cecba1 | |
Sebastiaan van Stijn | 67458f710d | |
Sebastiaan van Stijn | b45477bffa | |
Noah Silas | 0c999fe95b | |
Sebastiaan van Stijn | 5f1311ae8d | |
Sebastiaan van Stijn | 10c5a57927 | |
Sebastiaan van Stijn | 5e40d288c7 | |
Sebastiaan van Stijn | 9ba73a1a05 | |
Sebastiaan van Stijn | f3cf1b4213 | |
Sebastiaan van Stijn | cae19e3928 | |
Sebastiaan van Stijn | 074d1028b5 | |
Sebastiaan van Stijn | 1dbcce2057 | |
Sebastiaan van Stijn | 1bba009944 | |
Sebastiaan van Stijn | e3942d46a0 | |
Sebastiaan van Stijn | 97ff1b7c0a | |
Sebastiaan van Stijn | 4c85feb4dd | |
Sebastiaan van Stijn | 3b48a57b04 | |
Sebastiaan van Stijn | 36e6c42977 | |
Sebastiaan van Stijn | 84bfa52a6c | |
Sebastiaan van Stijn | 7a94f592ed | |
Sebastiaan van Stijn | ef197f7314 | |
Sebastiaan van Stijn | 02b92c699d | |
Laura Brehm | 2995631498 | |
Paweł Gronowski | fb103cb982 | |
Sebastiaan van Stijn | 42cda38840 | |
Sebastiaan van Stijn | 5e51513a8b | |
Giedrius Jonikas | 0b16070ae6 | |
Sebastiaan van Stijn | 9af049c618 | |
Sebastiaan van Stijn | 745629bd55 | |
Sebastiaan van Stijn | 7451339ab0 | |
Sebastiaan van Stijn | 020f3a7ad9 | |
Sebastiaan van Stijn | aa331e94cc | |
Sebastiaan van Stijn | 59b90305f7 | |
Sebastiaan van Stijn | 83e4ef4c3d |
|
@ -62,7 +62,7 @@ jobs:
|
|||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.8
|
||||
go-version: 1.23.3
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
|
|
|
@ -68,7 +68,7 @@ jobs:
|
|||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.8
|
||||
go-version: 1.23.3
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- copyloopvar # Detects places where loop variables are copied.
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupword # Detects duplicate words.
|
||||
- durationcheck
|
||||
- errchkjson
|
||||
- exportloopref # Detects pointers to enclosing loop variables.
|
||||
- gocritic # Metalinter; detects bugs, performance, and styling issues.
|
||||
- gocyclo
|
||||
- gofumpt # Detects whether code was gofumpt-ed.
|
||||
|
@ -41,6 +41,9 @@ linters:
|
|||
- errcheck
|
||||
|
||||
run:
|
||||
# prevent golangci-lint from deducting the go version to lint for through go.mod,
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
go: "1.23.3"
|
||||
timeout: 5m
|
||||
|
||||
linters-settings:
|
||||
|
@ -52,6 +55,13 @@ linters-settings:
|
|||
desc: The io/ioutil package has been deprecated, see https://go.dev/doc/go1.16#ioutil
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
gosec:
|
||||
excludes:
|
||||
- G104 # G104: Errors unhandled; (TODO: reduce unhandled errors, or explicitly ignore)
|
||||
- G113 # G113: Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772); (only affects go < 1.16.14. and go < 1.17.7)
|
||||
- G115 # G115: integer overflow conversion; (TODO: verify these: https://github.com/docker/cli/issues/5584)
|
||||
- G306 # G306: Expect WriteFile permissions to be 0600 or less (too restrictive; also flags "0o644" permissions)
|
||||
- G307 # G307: Deferring unsafe method "*os.File" on type "Close" (also EXC0008); (TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close")
|
||||
govet:
|
||||
enable:
|
||||
- shadow
|
||||
|
@ -87,6 +97,10 @@ issues:
|
|||
# The default exclusion rules are a bit too permissive, so copying the relevant ones below
|
||||
exclude-use-default: false
|
||||
|
||||
# This option has been defined when Go modules was not existed and when the
|
||||
# golangci-lint core was different, this is not something we still recommend.
|
||||
exclude-dirs-use-default: false
|
||||
|
||||
exclude:
|
||||
- parameter .* always receives
|
||||
|
||||
|
@ -104,6 +118,9 @@ issues:
|
|||
#
|
||||
# These exclusion patterns are copied from the default excluses at:
|
||||
# https://github.com/golangci/golangci-lint/blob/v1.44.0/pkg/config/issues.go#L10-L104
|
||||
#
|
||||
# The default list of exclusions can be found at:
|
||||
# https://golangci-lint.run/usage/false-positives/#default-exclusions
|
||||
|
||||
# EXC0001
|
||||
- text: "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print(f|ln)?|os\\.(Un)?Setenv). is not checked"
|
||||
|
@ -121,11 +138,6 @@ issues:
|
|||
- text: "Subprocess launch(ed with variable|ing should be audited)"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0008
|
||||
# TODO: evaluate these and fix where needed: G307: Deferring unsafe method "*os.File" on type "Close" (gosec)
|
||||
- text: "G307"
|
||||
linters:
|
||||
- gosec
|
||||
# EXC0009
|
||||
- text: "(Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)"
|
||||
linters:
|
||||
|
@ -135,26 +147,6 @@ issues:
|
|||
linters:
|
||||
- gosec
|
||||
|
||||
# G113 Potential uncontrolled memory consumption in Rat.SetString (CVE-2022-23772)
|
||||
# only affects gp < 1.16.14. and go < 1.17.7
|
||||
- text: "G113"
|
||||
linters:
|
||||
- gosec
|
||||
# TODO: G104: Errors unhandled. (gosec)
|
||||
- text: "G104"
|
||||
linters:
|
||||
- gosec
|
||||
# Looks like the match in "EXC0007" above doesn't catch this one
|
||||
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||
- text: "G204: Subprocess launched with a potential tainted input or cmd arguments"
|
||||
linters:
|
||||
- gosec
|
||||
# Looks like the match in "EXC0009" above doesn't catch this one
|
||||
# TODO: consider upstreaming this to golangci-lint's default exclusion rules
|
||||
- text: "G306: Expect WriteFile permissions to be 0600 or less"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# TODO: make sure all packages have a description. Currently, there's 67 packages without.
|
||||
- text: "package-comments: should have a package comment"
|
||||
linters:
|
||||
|
|
|
@ -0,0 +1,688 @@
|
|||
# Docker CLI design guidelines
|
||||
|
||||
This document provides guidelines to develop new features and enhancements
|
||||
for the Docker CLI.
|
||||
|
||||
|
||||
This is not an exhaustive set of rules, so in case of doubt, it may be good
|
||||
to discuss with a maintainer. This document is meant to be a living document;
|
||||
if you see something out of date or missing, pull requests are welcome.
|
||||
|
||||
This document intends to;
|
||||
|
||||
- Provide a consistent, predictable UX for users of the Docker CLI
|
||||
- Assist contributors and maintainers when reviewing changes, providing them
|
||||
guidelines to verify the design.
|
||||
|
||||
## General acceptance criteria
|
||||
|
||||
### Problem description
|
||||
|
||||
Features should address a problem or use-case. When contributing a new feature,
|
||||
describe the use-case or problem that it is addressing. Having actual examples
|
||||
not only helps to verify if the design matches the expectations, but can also
|
||||
assist other participants to verify if the proposed solution is the _only_
|
||||
(or "best") solution.
|
||||
|
||||
### Docker Compose file
|
||||
|
||||
Any feature added should usually also be added to the docker-compose schema;
|
||||
|
||||
- Propose new additions to the compose spec in the [Compose Spec](https://github.com/compose-spec/compose-spec)
|
||||
repository.
|
||||
- Update schema files [in this repository](https://github.com/docker/cli/tree/master/cli/compose/schema)
|
||||
|
||||
### Documentation
|
||||
|
||||
Any feature added should be accompanied by at least:
|
||||
|
||||
- A mention in the corresponding reference documentation.
|
||||
- An example in the reference documentation; the example
|
||||
should make clear _why_ a feature should be used (clear use-case)
|
||||
|
||||
For larger features, additional documentation may be needed in the main [documentation
|
||||
repository](https://github.com/docker/docker.github.io). The documentation team
|
||||
can help with writing that documentation, but technical assistance from contributors
|
||||
is generally appreciated.
|
||||
|
||||
### Completion scripts
|
||||
|
||||
New commands and flags should be added to the completion scripts. Help can be
|
||||
provided in updating those scripts; it's acceptable to update completion scripts
|
||||
in a follow-up pull requests, but a tracking issue must be created in that case.
|
||||
|
||||
- Bash completion (required)
|
||||
- PowerShell (optional)
|
||||
- Fish (optional)
|
||||
- Zsh (optional)
|
||||
|
||||
|
||||
## Technical / design debt
|
||||
|
||||
The Docker CLI evolved over time, which also means that the design inherited
|
||||
some design-choices from the past that may not have been the best choices (in
|
||||
hindsight), but cannot be changed without introducing breaking changes.
|
||||
|
||||
This section describes some of these behaviors.
|
||||
|
||||
### Legacy top level commands
|
||||
|
||||
Historically, the Docker CLI had a limited set of commands, to manage images
|
||||
and containers (`docker run`, `docker pull`, `docker push`). With the introduction
|
||||
of other type of objects (volumes, networks, services, plugins), this pattern
|
||||
did not scale.
|
||||
|
||||
No new top-level commands should be added; top-level commands are reserved for
|
||||
management commands going forward.
|
||||
|
||||
> **Note**: some less-frequently used legacy top-level commands can be hidden by
|
||||
> setting the `DOCKER_HIDE_LEGACY_COMMANDS` environment variable. Setting this
|
||||
> variable _hides_ the commands, but they will still remain active for backward
|
||||
> compatibility.
|
||||
|
||||
### Exit code for filtered results
|
||||
|
||||
When filtering results, and no results were found, the CLI produces a zero (success)
|
||||
exit code. Reason for this is that the action was _successful_, but happened to
|
||||
not produce any matching results.
|
||||
|
||||
For example;
|
||||
|
||||
```bash
|
||||
docker container ls --filter status=exited
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
|
||||
echo $?
|
||||
0
|
||||
```
|
||||
|
||||
```bash
|
||||
ls *.bla 2> /dev/null || echo "no such thing"
|
||||
no such thing
|
||||
```
|
||||
|
||||
|
||||
Producing a zero exit code can complicate using these commands in scripting
|
||||
situations, but has been considered too much of a breaking change to change (See
|
||||
[#27657](https://github.com/moby/moby/issues/27657#issuecomment-258271259) and
|
||||
[#28951](https://github.com/moby/moby/issues/28951)).
|
||||
|
||||
### Single-value flags can be specified multiple times
|
||||
|
||||
The Docker CLI accepts flags to be set multiple times, even if an option that's
|
||||
set through that flag accepts a single value. If a single-value flag is set
|
||||
multiple times, no error is produced, and latter values override prior values.
|
||||
|
||||
For example, the following command runs successfully;
|
||||
|
||||
```bash
|
||||
container create --name one --name two --name three busybox
|
||||
c872b39344646e86ef7d83f846e47f0cb92abc200938187f87052737026433d1
|
||||
|
||||
echo $?
|
||||
0
|
||||
```
|
||||
|
||||
And creates a container named `three`:
|
||||
|
||||
```bash
|
||||
docker container inspect --format '{{.Name}}' c872b39344646e86ef7d83f846e47f0cb92abc200938187f87052737026433d1
|
||||
/three
|
||||
```
|
||||
|
||||
> **Note**: it may be worth revisiting this situation, and only keep this
|
||||
> behavior for existing flags (for backward compatibility), and enforce single-
|
||||
> value flags to be specified only once going forward.
|
||||
|
||||
### Passing input from `stdin`
|
||||
|
||||
There is some inconsistency in notations used to pass input from `stdin`.
|
||||
|
||||
Some commands accept a path as positional argument, and `-` to accept input from
|
||||
`stdin`:
|
||||
|
||||
```
|
||||
Usage: docker build [OPTIONS] PATH | URL | -
|
||||
Usage: docker config create [OPTIONS] CONFIG file|-
|
||||
Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
||||
```
|
||||
|
||||
Whereas (e.g.) `docker load` _default_ to using `stdin/stdout`, and require `-i` /
|
||||
`--input` / `-o` / `--output` to be passed to use a file instead.
|
||||
|
||||
```
|
||||
Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||
Usage: docker load [OPTIONS]
|
||||
Usage: docker export [OPTIONS] CONTAINER
|
||||
```
|
||||
|
||||
Which allows passing
|
||||
|
||||
```bash
|
||||
echo $SOME_ENV_VAR | docker config create myconfig -
|
||||
|
||||
printf 'my configuration' | docker config create myconfig -
|
||||
|
||||
docker config create myconfig - <<< "my configuration"
|
||||
|
||||
docker config create myconfig - <<-'EOF'
|
||||
line 1
|
||||
line 2
|
||||
EOF
|
||||
```
|
||||
|
||||
### Sending output to `stdout`
|
||||
|
||||
|
||||
|
||||
> **Todo**: we need to describe the canonical approach going forward.
|
||||
|
||||
## General guidelines
|
||||
|
||||
### Keep it simple
|
||||
|
||||
Before contributing a feature, consider if the feature can be addressed through
|
||||
other means. Try to adhere to [the Linux principle](https://en.wikipedia.org/wiki/Unix_philosophy#Do_One_Thing_and_Do_It_Well);
|
||||
"do one thing, and do it well". If a use-case can be addressed combining
|
||||
commands (e.g. `docker container ls -q | xargs docker container inspect`).
|
||||
|
||||
|
||||
### Do not prematurely optimize usability
|
||||
|
||||
### Do not use shorthand (single-letter) flags
|
||||
|
||||
Shorthand, single-letter flags can easily become ambiguous (for example, `-f`
|
||||
can be either a shorthand for `--format` or for `--force`).
|
||||
|
||||
For this reason, shorthand flags must be reserved for frequently used options
|
||||
only, and only if there is a need. As with all changes, it is easier to add
|
||||
a shorthand option later, than to remove an option once added.
|
||||
|
||||
Standard flags/options are an exception to this rule, for example, list-commands
|
||||
that have a `--format` option should generally also get a `-f` shorthand.
|
||||
|
||||
### Avoid microformats
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### Configuration, and order of preference
|
||||
|
||||
1. Flag
|
||||
2. Environment variable
|
||||
3. Configuration
|
||||
|
||||
|
||||
|
||||
- not everything should be configurable. no direct need? don't do it
|
||||
|
||||
|
||||
## Linux principle, and "chainable"
|
||||
|
||||
|
||||
## Naming conventions
|
||||
|
||||
- Avoid product names: prefer "generic"
|
||||
- Describe what it _does_, not how the product is named
|
||||
|
||||
|
||||
|
||||
## API and feature compatibility
|
||||
|
||||
The Docker CLI should be compatible with older versions of the API. If a feature
|
||||
depends on a specific API version, or (for example) requires an orchestrator to
|
||||
be enabled, then the feature should be hidden if those conditions are not met.
|
||||
|
||||
For example, the following code defines a `--foo` flag that requires API version
|
||||
`1.99`. The flag will be hidden if the Docker CLI connects with a daemon that
|
||||
does not support this version of the API.
|
||||
|
||||
```go
|
||||
flags.BoolVar(&options.foo, "foo", false, "Foo enables foo on a container")
|
||||
flags.SetAnnotation("foo", "version", []string{"1.99"})
|
||||
```
|
||||
|
||||
The example below shows a `foo` command that requires a daemon with API version
|
||||
1.99 and experimental features enabled. In this example, `foo` is a top-level
|
||||
command, and all sub-commands will have the same requirements.
|
||||
|
||||
```go
|
||||
// NewFooCommand returns a cobra command for `foo` subcommands
|
||||
func NewFooCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "foo",
|
||||
Short: "Manage foos",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.99",
|
||||
"experimental": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newBarCommand(dockerCli),
|
||||
newBazCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Annotations exist for _orchestrators_ (kubernetes, swarm), experimental features,
|
||||
and builder version. Some annotations do not require a value, in which case `nil`
|
||||
should be used.
|
||||
|
||||
Annotation | Example value | Description
|
||||
----------------|---------------|------------------------------------------------------------------------------
|
||||
`version` | `1.40` | Feature requires API version `1.40`, and is hidden on any older API version.
|
||||
`experimental` | `nil` | Feature requires a daemon with experimental features enabled, and is hidden otherwise.
|
||||
`no-buildkit` | `nil` | Feature is only used when using the legacy builder, and is hidden if BuildKit is used as builder.
|
||||
`orchestrator` | `nil` | Feature requires an orchestrator (SwarmKit or Kubernetes) and is hidden otherwise.
|
||||
`kubernetes` | `nil` | Feature requires the Kubernetes orchestrator, and is hidden otherwise.
|
||||
`swarm` | `nil` | Feature requires the SwarmKit orchestrator, and is hidden otherwise.
|
||||
|
||||
|
||||
## Validation
|
||||
|
||||
Docker uses a client/server architecture. As a consequence, the environment in
|
||||
which the Docker CLI runs may not match the environment of the _daemon_. For
|
||||
this reason, validation on the client-side should be limited, and deferred as
|
||||
much as possible to the daemon.
|
||||
|
||||
Offloading validation to the daemon prevents validation-rules from diverging
|
||||
between both, and prevents the similar code having to be maintained _twice_. Or
|
||||
(worse) having validation code in the client, but unhandled by the daemon.
|
||||
|
||||
In addition; do not expect what's valid to never change (what's invalid today,
|
||||
may be supported by the daemon, or the kernel, tomorrow).
|
||||
|
||||
As a rule of thumb: it's the CLI's responsibility to convert commands and arguments
|
||||
into API requests. If the CLI is able to handle a value, and turn it into an a
|
||||
valid API request (even if values in the request are invalid), that responsibility
|
||||
is met. If the request fails, it should handle the error, and (where needed)
|
||||
present it to the user.
|
||||
|
||||
Some examples of validations:
|
||||
|
||||
|
||||
OK | Validation | Description
|
||||
------------------------|---------------------------------------------------|-------------------------------------
|
||||
:white_check_mark: | Check if a required command-line argument is set | If a command requires an argument, it is ok to check if the argument is provided.
|
||||
:white_check_mark: | Validate if a numeric value only contains numbers | This is ok. If the API requires a numeric value, the CLI should be able to convert the user-provided value.
|
||||
:x: | Check if a name only contains allowed characters | Rules for (e.g.) names can change over time, thus differ between daemon versions. This validation should be done by the daemon, and returned as an error.
|
||||
:white_check_mark: | Validate if a file exists before uploading it | This is ok. The client must be able to access the file (or directory) in order to upload it.
|
||||
:x: | Check if the host-path of a bind-mount exists | Bind-mounts are done on the host where the daemon runs, therefore validation of these paths should not be done by the CLI.
|
||||
:large_orange_diamond: | Validate if an URL option is well-formed | Generally this should be ok if the API expects an URL value. Consider if the validation adds value; if the CLI is able to create an API request, even if the value is invalid, validation could be offloaded to the daemon.
|
||||
|
||||
## Sanitizing and normalizing user input
|
||||
|
||||
Avoid string manipulation, unless necessary. Prefer strictness of user-provided
|
||||
values over "fuzzy" matching / "guessing" user intent. Producing an error, and
|
||||
loosen validation over time can be done without breaking backward compatibility
|
||||
(it's guaranteed that no user was using the invalid value), whereas becoming
|
||||
more strict requires a deprecation cycle ("xx is no longer valid, and support
|
||||
will be removed in release XX.YY").
|
||||
|
||||
OK | Validation | Description
|
||||
------------------------|---------------------------------------------------|-------------------------------------
|
||||
:white_check_mark: | Trim leading and trailing whitespace | Generally ok
|
||||
:large_orange_diamond: | Sort values | Tread carefully. Sorting may be required to prevent Swarm services from being updated if no changes were made, but in some cases "order matters".
|
||||
:x: | Strip quotes | Should be handled by the shell
|
||||
:x: | Cast strings to lowercase / uppercase | Avoid string value manipulation if not needed.
|
||||
|
||||
|
||||
|
||||
### Commands and subcommands
|
||||
|
||||
#### Standardized "CRUD" commands and aliases
|
||||
|
||||
- `create`
|
||||
- `remove`, `rm`
|
||||
- `update`
|
||||
- `list`, `ls`
|
||||
- `inspect` (JSON)
|
||||
|
||||
Command | Aliases | Description
|
||||
---------------------------------|-------------|--------------------------------------------------
|
||||
`docker <object> create` | - | Creates a new `<object>`
|
||||
`docker <object> list` | `ls` / `ps` | Presents a list of `<object>` (table view by default)
|
||||
`docker <object> inspect <id>` | - | Provides low-level information about `<object> <id>` in JSON format
|
||||
`docker <object> update <id>` | - | Updates `<object> <id>`
|
||||
`docker <object> remove <id>` | `rm` |
|
||||
`docker <object> prune` | - |
|
||||
|
||||
|
||||
|
||||
|
||||
### Flags
|
||||
|
||||
#### Shorthand (single-letter) flags
|
||||
|
||||
|
||||
|
||||
#### Standardized flags
|
||||
|
||||
Flag | Generally used on | Description
|
||||
------------------|---------------------|----------------------------------------------------------------
|
||||
`--format` / `-f` | `list`, `inspect` | Pretty-print objects using a Go template
|
||||
`--filter` / `-f` | `list` | Filter objects based on conditions provided
|
||||
`--no-trunc` | `list` | List outputs can truncate columns to save screen-space. The `--no-trunc` option prints columns without truncating
|
||||
`--quiet` / `-q` | `list` | For list outputs; only print object ID's.
|
||||
|
||||
|
||||
|
||||
|
||||
#### File flags
|
||||
|
||||
- absolute vs relative paths
|
||||
- support for stdin (CONVENTION??)
|
||||
|
||||
### Feedback on successful operations
|
||||
|
||||
Successful operations on an object should print the object's identifier on `stdout`
|
||||
on success; doing so enables users to consume the output for scripting.
|
||||
|
||||
For example, the following command creates two volumes, using different drivers,
|
||||
and attaches those volumes to a new container:
|
||||
|
||||
```bash
|
||||
docker container run \
|
||||
--volume $(docker volume create --driver=foo):/somewhere \
|
||||
--volume $(docker volume create --driver=bar):/somewhere-else \
|
||||
busybox
|
||||
```
|
||||
|
||||
|
||||
Either `ID` or `name` are acceptable, as long as the identifier can be used
|
||||
to reference the object.
|
||||
|
||||
Some examples of commands that follow this design:
|
||||
|
||||
|
||||
Creating a container prints the container's `ID` on `stdout`
|
||||
|
||||
```bash
|
||||
docker container create --name test busybox
|
||||
cc1555ede50a11f02a3ef6cc8eedd9f78f6299a2f2b7efdc8a91fbefb8fc194a
|
||||
```
|
||||
|
||||
|
||||
Removing a container prints the reference that was given
|
||||
|
||||
```bash
|
||||
docker container rm test
|
||||
test
|
||||
|
||||
docker container rm 057d35243903e522c70ae2dfab5f706e4ea3cc1ae4c38cce955a99be3d09cfd1
|
||||
057d35243903e522c70ae2dfab5f706e4ea3cc1ae4c38cce955a99be3d09cfd1
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Use of stdout and stderr
|
||||
|
||||
|
||||
As a rule of thumb;
|
||||
|
||||
- Use `stdout` to print the _expected_ output of a command
|
||||
- Where possible make `stdout` usable for scripting
|
||||
- Use `stderr` non-standard output (errors), and for informational messages.
|
||||
|
||||
Sometimes these differences are subtle.
|
||||
|
||||
#### Example: usage information.
|
||||
|
||||
The `docker` command expects a subcommand. If no subcommand is given, the Docker
|
||||
CLI prints an informational message, showing the usage information:
|
||||
|
||||
```bash
|
||||
docker
|
||||
|
||||
Usage: docker [OPTIONS] COMMAND
|
||||
|
||||
A self-sufficient runtime for containers
|
||||
...
|
||||
```
|
||||
|
||||
Typing `docker --help` also prints the usage information:
|
||||
|
||||
```bash
|
||||
docker --help
|
||||
|
||||
Usage: docker [OPTIONS] COMMAND
|
||||
|
||||
A self-sufficient runtime for containers
|
||||
...
|
||||
```
|
||||
|
||||
At a glance, both appear to be identical, but there is a difference:
|
||||
|
||||
In the first example, `stdout` contains the "expected" output of the `docker`
|
||||
command; the `docker` command requires a subcommand, and by itself does not
|
||||
produce a result.
|
||||
|
||||
The "usage" output is informational, and therefore printed on `stderr`, which
|
||||
can be seen when discarding the `stderr` output by redirecting it to `/dev/null`;
|
||||
|
||||
```bash
|
||||
docker 2> /dev/null
|
||||
# no output (stdout contains no output)
|
||||
```
|
||||
|
||||
When using the `--help` flag, the usage information is the _expected_ output,
|
||||
and therefore printed on `stdout`. Suppressing `stderr` output shows that all
|
||||
output is this time printed on `stdout`:
|
||||
|
||||
```bash
|
||||
docker --help 2> /dev/null
|
||||
|
||||
Usage: docker [OPTIONS] COMMAND
|
||||
|
||||
A self-sufficient runtime for containers
|
||||
```
|
||||
|
||||
|
||||
#### Practical example: docker run
|
||||
|
||||
|
||||
logging informational messages and consuming stdout output
|
||||
|
||||
```bash
|
||||
docker container run -d nginx:alpine | xargs docker container inspect --format 'the name of the started container is: {{.Name}}'
|
||||
|
||||
Unable to find image 'nginx:alpine' locally
|
||||
alpine: Pulling from library/nginx
|
||||
cd784148e348: Already exists
|
||||
6e3058b2db8a: Already exists
|
||||
7ca4d29669c1: Already exists
|
||||
a14cf6997716: Already exists
|
||||
Digest: sha256:385fbcf0f04621981df6c6f1abd896101eb61a439746ee2921b26abc78f45571
|
||||
Status: Downloaded newer image for nginx:alpine
|
||||
the name of the started container is: /vigilant_zhukovsky
|
||||
```
|
||||
|
||||
|
||||
|
||||
```bash
|
||||
docker container run -d nginx:alpine 2> ./err.log | xargs docker container inspect --format '{{.Name}}'
|
||||
/cranky_jepsen
|
||||
|
||||
|
||||
cat err.log
|
||||
Unable to find image 'nginx:alpine' locally
|
||||
alpine: Pulling from library/nginx
|
||||
cd784148e348: Already exists
|
||||
6e3058b2db8a: Already exists
|
||||
7ca4d29669c1: Already exists
|
||||
a14cf6997716: Already exists
|
||||
Digest: sha256:385fbcf0f04621981df6c6f1abd896101eb61a439746ee2921b26abc78f45571
|
||||
Status: Downloaded newer image for nginx:alpine
|
||||
```
|
||||
|
||||
|
||||
|
||||
```
|
||||
docker service create nginx:alpine
|
||||
trppp1sywrkegq8e4pynjenv0
|
||||
overall progress: 1 out of 1 tasks
|
||||
1/1: running [==================================================>]
|
||||
verify: Service converged
|
||||
```
|
||||
|
||||
```
|
||||
docker service create nginx:alpine 2> /dev/null
|
||||
clu6qi8pcg2fbnhxpuusdk0iw
|
||||
overall progress: 1 out of 1 tasks
|
||||
1/1: running [==================================================>]
|
||||
verify: Service converged
|
||||
```
|
||||
|
||||
```
|
||||
docker service create --detach nginx:alpine
|
||||
q5d2k2bbgk6uh2rs3g4udfdh7
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Object identifiers
|
||||
|
||||
Objects can have multiple identifiers, for example, containers have both an
|
||||
`ID` and a `name`, both of which must be unique (and are thus interchangeable).
|
||||
Names are allowed to be mutable (for example, a container can be renamed using
|
||||
the `docker container rename` command).
|
||||
|
||||
Objects that have both an `ID` and `name`, and where commands accept either an
|
||||
`ID`, a `name` or a _partial_ `ID` (ID-prefix) should address ambiguity by
|
||||
prioritizing as below:
|
||||
|
||||
1. Full, non-truncated `ID`
|
||||
2. `name` (full match only, no prefix matching)
|
||||
3. Partial `ID` (prefix matching)
|
||||
|
||||
If multiple objects match a given ID prefix, an error must be produced, stating
|
||||
that the given prefix is ambiguous.
|
||||
|
||||
For example, given the following containers:
|
||||
|
||||
```bash
|
||||
docker ps --no-trunc --format 'table {{.ID}}\t{{.Names}}'
|
||||
|
||||
CONTAINER ID NAMES
|
||||
70d50d097b5597d8d08171e6be51cee9e15083c5b92dde134639c4105db2a40d mycontainer01
|
||||
724f22e1e32c7cc8d80383f06baebbcd0f5fec0e5592bdf6b50b2bcd8d391ab7 70d50d097b55
|
||||
737ff55584e28badc5fdccd22582f6d4583d0f792cbb4924493f5d341e44a278 70d50d097b5597d8d08171e6be51cee9e15083c5b92dde134639c4105db2a40d
|
||||
```
|
||||
|
||||
Note that:
|
||||
|
||||
- All containers have an `ID` starting with `7`
|
||||
- The second container's name matches the first container's "short" `ID` (ID-prefix)
|
||||
- The third container's name matches the first container's full `ID`
|
||||
|
||||
Running `docker container inspect` using the first container's _full_ ID produces
|
||||
the first container (full `ID` takes precedence over full name);
|
||||
|
||||
```bash
|
||||
docker container inspect 70d50d097b5597d8d08171e6be51cee9e15083c5b92dde134639c4105db2a40d --format '{{.Name}}'
|
||||
/mycontainer01
|
||||
```
|
||||
|
||||
Inspecting using the second container's full name, produces that container,
|
||||
_even though it also matches a prefix of the first container's ID_ (full name match
|
||||
takes precedence over a partial `ID` match):
|
||||
|
||||
```bash
|
||||
docker container inspect 70d50d097b55 --format '{{.Name}}'
|
||||
/70d50d097b55
|
||||
```
|
||||
|
||||
Inspecting using a _prefix_ produces an error, because multiple objects are matched:
|
||||
|
||||
```bash
|
||||
docker container inspect 7 --format '{{.Name}}'
|
||||
Error response from daemon: Multiple IDs found with provided prefix: 7
|
||||
```
|
||||
|
||||
Using a longer prefix will succeed if the prefix is non-ambiguous;
|
||||
|
||||
```bash
|
||||
docker container inspect 70 --format '{{.Name}}'
|
||||
/mycontainer01
|
||||
```
|
||||
|
||||
|
||||
|
||||
#### Progressbars
|
||||
|
||||
- automatically disabled if no terminal is detected
|
||||
- also through `--quiet` / `-q` flag
|
||||
|
||||
|
||||
#### Boolean flags should expect no value
|
||||
|
||||
**exception** flags that will change their default in the near future
|
||||
|
||||
```bash
|
||||
--disable-some-feature=false
|
||||
--enable-almost-deprecated-feature=false
|
||||
```
|
||||
|
||||
#### Shorthand flag formats
|
||||
|
||||
- avoid microformats
|
||||
- keep Windows/Linux into account
|
||||
- strict > permissive (easier to be less restrictive in future than the other way round)
|
||||
- case-sensitive values (`none` != `None` != `NONE` != `nOnE`)
|
||||
|
||||
#### Long form (advanced) syntax
|
||||
|
||||
- group all options for a configuration in a single flag
|
||||
- allow that flag to be set multiple times, and still being able to group those
|
||||
options together (example: `--volume-driver`, `--volume`, which prevented
|
||||
multiple drivers to be used)
|
||||
|
||||
|
||||
### Positional arguments
|
||||
|
||||
Positional arguments are generally more suitable for "required" arguments, whereas flags are for "optional" arguments.
|
||||
|
||||
Examples:
|
||||
|
||||
Creating a container does not require a name to be specified (a name is generated when omitted), hence, the name
|
||||
is passed as
|
||||
|
||||
```bash
|
||||
docker container create busybox:latest
|
||||
47931878b128c40281aa0254914c3a437b2fdf91133a1620b3152e05d3c81e5d
|
||||
|
||||
docker container create --name mycontainer busybox:latest
|
||||
908a6f802740d9a67861fc031c62d6241fbb9f16b9388114b14cf120e4f0cd68
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Use of stdout and stderr
|
||||
|
||||
|
||||
- output should be useful
|
||||
-
|
||||
|
||||
|
||||
## Exit codes
|
||||
|
||||
- exit code for list commands (exit code 0 for "no results")
|
||||
|
||||
|
||||
## Filtering
|
||||
|
||||
## Formatting output
|
||||
|
||||
- Discuss `JSON` output option
|
||||
- Presentation should not be in `JSON` output (or the API for that matter)
|
||||
- Configurable in `~/.docker/config.json`
|
||||
|
||||
|
|
@ -4,12 +4,12 @@ ARG BASE_VARIANT=alpine
|
|||
ARG ALPINE_VERSION=3.20
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.22.8
|
||||
ARG GO_VERSION=1.23.3
|
||||
ARG XX_VERSION=1.5.0
|
||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||
ARG GOTESTSUM_VERSION=v1.10.0
|
||||
ARG BUILDX_VERSION=0.17.1
|
||||
ARG COMPOSE_VERSION=v2.29.7
|
||||
ARG BUILDX_VERSION=0.18.0
|
||||
ARG COMPOSE_VERSION=v2.30.3
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
|
|
|
@ -17,5 +17,5 @@ func (c *candidate) Path() string {
|
|||
}
|
||||
|
||||
func (c *candidate) Metadata() ([]byte, error) {
|
||||
return exec.Command(c.path, MetadataSubcommandName).Output()
|
||||
return exec.Command(c.path, MetadataSubcommandName).Output() // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
||||
}
|
||||
|
|
|
@ -52,7 +52,6 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
|||
return
|
||||
}
|
||||
for _, p := range plugins {
|
||||
p := p
|
||||
vendor := p.Vendor
|
||||
if vendor == "" {
|
||||
vendor = "unknown"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package manager
|
||||
|
||||
|
|
|
@ -240,7 +240,8 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
|
|||
// TODO: why are we not returning plugin.Err?
|
||||
return nil, errPluginNotFound(name)
|
||||
}
|
||||
cmd := exec.Command(plugin.Path, args...)
|
||||
cmd := exec.Command(plugin.Path, args...) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
||||
|
||||
// Using dockerCli.{In,Out,Err}() here results in a hang until something is input.
|
||||
// See: - https://github.com/golang/go/issues/10338
|
||||
// - https://github.com/golang/go/commit/d000e8742a173aa0659584aa01b7ba2834ba28ab
|
||||
|
|
|
@ -112,7 +112,7 @@ func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte,
|
|||
return nil, wrapAsPluginError(err, "failed to marshall hook data")
|
||||
}
|
||||
|
||||
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes))
|
||||
pCmd := exec.CommandContext(ctx, p.Path, p.Name, HookSubcommandName, string(hDataBytes)) // #nosec G204 -- ignore "Subprocess launched with a potential tainted input or cmd arguments"
|
||||
pCmd.Env = os.Environ()
|
||||
pCmd.Env = append(pCmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||
hookCmdOutput, err := pCmd.Output()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package command
|
||||
|
||||
|
|
|
@ -187,19 +187,18 @@ func TestInitializeFromClient(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
testcase := testcase
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
apiclient := &fakeClient{
|
||||
pingFunc: testcase.pingFunc,
|
||||
pingFunc: tc.pingFunc,
|
||||
version: defaultVersion,
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient}
|
||||
err := cli.Initialize(flags.NewClientOptions())
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, cli.ServerInfo(), testcase.expectedServer)
|
||||
assert.Equal(t, apiclient.negotiated, testcase.negotiated)
|
||||
assert.DeepEqual(t, cli.ServerInfo(), tc.expectedServer)
|
||||
assert.Equal(t, apiclient.negotiated, tc.negotiated)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -277,10 +276,9 @@ func TestExperimentalCLI(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
testcase := testcase
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, tc.doc, fs.WithFile("config.json", tc.configfile))
|
||||
defer dir.Remove()
|
||||
apiclient := &fakeClient{
|
||||
version: defaultVersion,
|
||||
|
|
|
@ -150,7 +150,6 @@ func TestCompleteContainerNames(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
if tc.showIDs {
|
||||
t.Setenv("DOCKER_COMPLETION_SHOW_CONTAINER_IDS", "yes")
|
||||
|
@ -227,7 +226,6 @@ func TestCompleteImageNames(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
comp := ImageNames(fakeCLI{&fakeClient{
|
||||
imageListFunc: func(options image.ListOptions) ([]image.Summary, error) {
|
||||
|
@ -273,7 +271,6 @@ func TestCompleteNetworkNames(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
comp := NetworkNames(fakeCLI{&fakeClient{
|
||||
networkListFunc: func(ctx context.Context, options network.ListOptions) ([]network.Summary, error) {
|
||||
|
@ -331,7 +328,6 @@ func TestCompleteVolumeNames(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
comp := VolumeNames(fakeCLI{&fakeClient{
|
||||
volumeListFunc: func(filter filters.Args) (volume.ListResponse, error) {
|
||||
|
|
|
@ -43,7 +43,6 @@ func TestConfigCreateErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.expectedError, func(t *testing.T) {
|
||||
cmd := newConfigCreateCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
|
|
|
@ -61,7 +61,6 @@ id_rsa
|
|||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package config
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ func TestNewAttachCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
|
|
@ -44,6 +44,65 @@ var allLinuxCapabilities = sync.OnceValue(func() []string {
|
|||
return out
|
||||
})
|
||||
|
||||
// logDriverOptions provides the options for each built-in logging driver.
|
||||
var logDriverOptions = map[string][]string{
|
||||
"awslogs": {
|
||||
"max-buffer-size", "mode", "awslogs-create-group", "awslogs-credentials-endpoint", "awslogs-datetime-format",
|
||||
"awslogs-group", "awslogs-multiline-pattern", "awslogs-region", "awslogs-stream", "tag",
|
||||
},
|
||||
"fluentd": {
|
||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "fluentd-address", "fluentd-async",
|
||||
"fluentd-buffer-limit", "fluentd-request-ack", "fluentd-retry-wait", "fluentd-max-retries",
|
||||
"fluentd-sub-second-precision", "tag",
|
||||
},
|
||||
"gcplogs": {
|
||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "gcp-log-cmd", "gcp-meta-id", "gcp-meta-name",
|
||||
"gcp-meta-zone", "gcp-project",
|
||||
},
|
||||
"gelf": {
|
||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "gelf-address", "gelf-compression-level",
|
||||
"gelf-compression-type", "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay", "tag",
|
||||
},
|
||||
"journald": {"max-buffer-size", "mode", "env", "env-regex", "labels", "tag"},
|
||||
"json-file": {"max-buffer-size", "mode", "env", "env-regex", "labels", "compress", "max-file", "max-size"},
|
||||
"local": {"max-buffer-size", "mode", "compress", "max-file", "max-size"},
|
||||
"none": {},
|
||||
"splunk": {
|
||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "splunk-caname", "splunk-capath", "splunk-format",
|
||||
"splunk-gzip", "splunk-gzip-level", "splunk-index", "splunk-insecureskipverify", "splunk-source",
|
||||
"splunk-sourcetype", "splunk-token", "splunk-url", "splunk-verify-connection", "tag",
|
||||
},
|
||||
"syslog": {
|
||||
"max-buffer-size", "mode", "env", "env-regex", "labels", "syslog-address", "syslog-facility", "syslog-format",
|
||||
"syslog-tls-ca-cert", "syslog-tls-cert", "syslog-tls-key", "syslog-tls-skip-verify", "tag",
|
||||
},
|
||||
}
|
||||
|
||||
// builtInLogDrivers provides a list of the built-in logging drivers.
|
||||
var builtInLogDrivers = sync.OnceValue(func() []string {
|
||||
drivers := make([]string, 0, len(logDriverOptions))
|
||||
for driver := range logDriverOptions {
|
||||
drivers = append(drivers, driver)
|
||||
}
|
||||
return drivers
|
||||
})
|
||||
|
||||
// allLogDriverOptions provides all options of the built-in logging drivers.
|
||||
// The list does not contain duplicates.
|
||||
var allLogDriverOptions = sync.OnceValue(func() []string {
|
||||
var result []string
|
||||
seen := make(map[string]bool)
|
||||
for driver := range logDriverOptions {
|
||||
for _, opt := range logDriverOptions[driver] {
|
||||
if !seen[opt] {
|
||||
seen[opt] = true
|
||||
result = append(result, opt)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
// restartPolicies is a list of all valid restart-policies..
|
||||
//
|
||||
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
|
||||
|
@ -54,6 +113,207 @@ var restartPolicies = []string{
|
|||
string(container.RestartPolicyUnlessStopped),
|
||||
}
|
||||
|
||||
// addCompletions adds the completions that `run` and `create` have in common.
|
||||
func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) {
|
||||
_ = cmd.RegisterFlagCompletionFunc("attach", completion.FromList("stderr", "stdin", "stdout"))
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("cgroupns", completeCgroupns())
|
||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("log-driver", completeLogDriver(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("log-opt", completeLogOpt)
|
||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("pid", completePid(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||
_ = cmd.RegisterFlagCompletionFunc("security-opt", completeSecurityOpt)
|
||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||
_ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt)
|
||||
_ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit)
|
||||
_ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host"))
|
||||
_ = cmd.RegisterFlagCompletionFunc("uts", completion.FromList("host"))
|
||||
_ = cmd.RegisterFlagCompletionFunc("volume-driver", completeVolumeDriver(dockerCLI))
|
||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true))
|
||||
}
|
||||
|
||||
// completeCgroupns implements shell completion for the `--cgroupns` option of `run` and `create`.
|
||||
func completeCgroupns() completion.ValidArgsFn {
|
||||
return completion.FromList(string(container.CgroupnsModeHost), string(container.CgroupnsModePrivate))
|
||||
}
|
||||
|
||||
// completeDetachKeys implements shell completion for the `--detach-keys` option of `run` and `create`.
|
||||
func completeDetachKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"ctrl-"}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
|
||||
// completeIpc implements shell completion for the `--ipc` option of `run` and `create`.
|
||||
// The completion is partly composite.
|
||||
func completeIpc(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
|
||||
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
if strings.HasPrefix(toComplete, "container:") {
|
||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return []string{
|
||||
string(container.IPCModeContainer + ":"),
|
||||
string(container.IPCModeHost),
|
||||
string(container.IPCModeNone),
|
||||
string(container.IPCModePrivate),
|
||||
string(container.IPCModeShareable),
|
||||
}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// completeLink implements shell completion for the `--link` option of `run` and `create`.
|
||||
func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return postfixWith(":", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
}
|
||||
|
||||
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
|
||||
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
|
||||
// of the build-in log drivers.
|
||||
func completeLogDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
info, err := dockerCLI.Client().Info(cmd.Context())
|
||||
if err != nil {
|
||||
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
drivers := info.Plugins.Log
|
||||
return drivers, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// completeLogOpt implements shell completion for the `--log-opt` option of `run` and `create`.
|
||||
// If the user supplied a log-driver, only options for that driver are returned.
|
||||
func completeLogOpt(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
driver, _ := cmd.Flags().GetString("log-driver")
|
||||
if options, exists := logDriverOptions[driver]; exists {
|
||||
return postfixWith("=", options), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return postfixWith("=", allLogDriverOptions()), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
|
||||
// completePid implements shell completion for the `--pid` option of `run` and `create`.
|
||||
func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
|
||||
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
if strings.HasPrefix(toComplete, "container:") {
|
||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return []string{"container:", "host"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// completeSecurityOpt implements shell completion for the `--security-opt` option of `run` and `create`.
|
||||
// The completion is partly composite.
|
||||
func completeSecurityOpt(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(toComplete) > 0 && strings.HasPrefix("apparmor=", toComplete) { //nolint:gocritic // not swapped, matches partly typed "apparmor="
|
||||
return []string{"apparmor="}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
if len(toComplete) > 0 && strings.HasPrefix("label", toComplete) { //nolint:gocritic // not swapped, matches partly typed "label"
|
||||
return []string{"label="}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
if strings.HasPrefix(toComplete, "label=") {
|
||||
if strings.HasPrefix(toComplete, "label=d") {
|
||||
return []string{"label=disable"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
labels := []string{"disable", "level:", "role:", "type:", "user:"}
|
||||
return prefixWith("label=", labels), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
// length must be > 1 here so that completion of "s" falls through.
|
||||
if len(toComplete) > 1 && strings.HasPrefix("seccomp", toComplete) { //nolint:gocritic // not swapped, matches partly typed "seccomp"
|
||||
return []string{"seccomp="}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
if strings.HasPrefix(toComplete, "seccomp=") {
|
||||
return []string{"seccomp=unconfined"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
// completeStorageOpt implements shell completion for the `--storage-opt` option of `run` and `create`.
|
||||
func completeStorageOpt(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return []string{"size="}, cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
|
||||
// completeUlimit implements shell completion for the `--ulimit` option of `run` and `create`.
|
||||
func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
limits := []string{
|
||||
"as",
|
||||
"chroot",
|
||||
"core",
|
||||
"cpu",
|
||||
"data",
|
||||
"fsize",
|
||||
"locks",
|
||||
"maxlogins",
|
||||
"maxsyslogins",
|
||||
"memlock",
|
||||
"msgqueue",
|
||||
"nice",
|
||||
"nofile",
|
||||
"nproc",
|
||||
"priority",
|
||||
"rss",
|
||||
"rtprio",
|
||||
"sigpending",
|
||||
"stack",
|
||||
}
|
||||
return postfixWith("=", limits), cobra.ShellCompDirectiveNoSpace
|
||||
}
|
||||
|
||||
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers.
|
||||
func completeVolumeDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
||||
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
info, err := dockerCLI.Client().Info(cmd.Context())
|
||||
if err != nil {
|
||||
// fallback: the built-in drivers
|
||||
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
drivers := info.Plugins.Volume
|
||||
return drivers, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
// containerNames contacts the API to get names and optionally IDs of containers.
|
||||
// In case of an error, an empty list is returned.
|
||||
func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command, args []string, toComplete string) []string {
|
||||
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||
if names == nil {
|
||||
return []string{}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// prefixWith prefixes every element in the slice with the given prefix.
|
||||
func prefixWith(prefix string, values []string) []string {
|
||||
result := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
result[i] = prefix + v
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// postfixWith appends postfix to every element in the slice.
|
||||
func postfixWith(postfix string, values []string) []string {
|
||||
result := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
result[i] = v + postfix
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
||||
return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/builders"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/sys/signal"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
|
@ -21,6 +24,48 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCompletePid(t *testing.T) {
|
||||
tests := []struct {
|
||||
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
||||
toComplete string
|
||||
expectedCompletions []string
|
||||
expectedDirective cobra.ShellCompDirective
|
||||
}{
|
||||
{
|
||||
toComplete: "",
|
||||
expectedCompletions: []string{"container:", "host"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
toComplete: "c",
|
||||
expectedCompletions: []string{"container:"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
containerListFunc: func(container.ListOptions) ([]container.Summary, error) {
|
||||
return []container.Summary{
|
||||
*builders.Container("c1"),
|
||||
*builders.Container("c2"),
|
||||
}, nil
|
||||
},
|
||||
toComplete: "container:",
|
||||
expectedCompletions: []string{"container:c1", "container:c2"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.toComplete, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: tc.containerListFunc,
|
||||
})
|
||||
completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete)
|
||||
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
||||
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteRestartPolicies(t *testing.T) {
|
||||
values, directives := completeRestartPolicies(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||
|
@ -28,6 +73,59 @@ func TestCompleteRestartPolicies(t *testing.T) {
|
|||
assert.Check(t, is.DeepEqual(values, expected))
|
||||
}
|
||||
|
||||
func TestCompleteSecurityOpt(t *testing.T) {
|
||||
tests := []struct {
|
||||
toComplete string
|
||||
expectedCompletions []string
|
||||
expectedDirective cobra.ShellCompDirective
|
||||
}{
|
||||
{
|
||||
toComplete: "",
|
||||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
toComplete: "apparmor=",
|
||||
expectedCompletions: []string{"apparmor="},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
toComplete: "label=",
|
||||
expectedCompletions: []string{"label=disable", "label=level:", "label=role:", "label=type:", "label=user:"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
toComplete: "s",
|
||||
// We do not filter matching completions but delegate this task to the shell script.
|
||||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
toComplete: "se",
|
||||
expectedCompletions: []string{"seccomp="},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||
},
|
||||
{
|
||||
toComplete: "seccomp=",
|
||||
expectedCompletions: []string{"seccomp=unconfined"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
{
|
||||
toComplete: "sy",
|
||||
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.toComplete, func(t *testing.T) {
|
||||
completions, directive := completeSecurityOpt(nil, nil, tc.toComplete)
|
||||
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
||||
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompleteSignals(t *testing.T) {
|
||||
values, directives := completeSignals(nil, nil, "")
|
||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||
|
|
|
@ -178,7 +178,6 @@ func TestSplitCpArg(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
if tc.os == "windows" && runtime.GOOS != "windows" {
|
||||
t.Skip("skipping windows test on non-windows platform")
|
||||
|
|
|
@ -78,16 +78,15 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||
copts = addFlags(flags)
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
||||
addCompletions(cmd, dockerCli)
|
||||
|
||||
flags.VisitAll(func(flag *pflag.Flag) {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
@ -113,7 +113,6 @@ func TestCreateContainerImagePullPolicy(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||
pullCounter := 0
|
||||
|
||||
|
@ -176,7 +175,6 @@ func TestCreateContainerImagePullPolicyInvalid(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||
dockerCli := test.NewFakeCli(&fakeClient{})
|
||||
err := runCreate(
|
||||
|
@ -207,7 +205,6 @@ func TestCreateContainerValidateFlags(t *testing.T) {
|
|||
expectedErr: `invalid argument "STDINFO" for "-a, --attach" flag: valid streams are STDIN, STDOUT and STDERR`,
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewCreateCommand(test.NewFakeCli(&fakeClient{}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
@ -251,7 +248,6 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
|
@ -312,7 +308,6 @@ func TestNewCreateCommandWithWarnings(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
|
|
|
@ -47,7 +47,6 @@ D: /usr/app/old_app.js
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
out := bytes.NewBufferString("")
|
||||
tc.context.Output = out
|
||||
|
|
|
@ -178,7 +178,6 @@ container2 -- --
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
@ -223,7 +222,6 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := statsFormatWrite(tc.context, []StatsEntry{}, "linux", false)
|
||||
assert.NilError(t, err)
|
||||
|
@ -265,7 +263,6 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := statsFormatWrite(tc.context, []StatsEntry{}, "windows", false)
|
||||
assert.NilError(t, err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package container
|
||||
|
||||
|
|
|
@ -277,7 +277,6 @@ func TestContainerListFormatSizeSetsOption(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerListFunc: func(options container.ListOptions) ([]container.Summary, error) {
|
||||
|
|
|
@ -23,7 +23,6 @@ import (
|
|||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
cdi "tags.cncf.io/container-device-interface/pkg/parser"
|
||||
)
|
||||
|
@ -364,10 +363,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
|||
return nil, errors.Errorf("invalid value: %d. Valid memory swappiness range is 0-100", swappiness)
|
||||
}
|
||||
|
||||
mounts := copts.mounts.Value()
|
||||
if len(mounts) > 0 && copts.volumeDriver != "" {
|
||||
logrus.Warn("`--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead.")
|
||||
}
|
||||
var binds []string
|
||||
volumes := copts.volumes.GetMap()
|
||||
// add any bind targets to the list of container volumes
|
||||
|
@ -697,7 +692,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
|||
Tmpfs: tmpfs,
|
||||
Sysctls: copts.sysctls.GetAll(),
|
||||
Runtime: copts.runtime,
|
||||
Mounts: mounts,
|
||||
Mounts: copts.mounts.Value(),
|
||||
MaskedPaths: maskedPaths,
|
||||
ReadonlyPaths: readonlyPaths,
|
||||
Annotations: copts.annotations.GetAll(),
|
||||
|
@ -767,7 +762,6 @@ func parseNetworkOpts(copts *containerOptions) (map[string]*networktypes.Endpoin
|
|||
}
|
||||
|
||||
for i, n := range copts.netMode.Value() {
|
||||
n := n
|
||||
if container.NetworkMode(n.Target).IsUserDefined() {
|
||||
hasUserDefined = true
|
||||
} else {
|
||||
|
|
|
@ -126,7 +126,6 @@ func TestParseRunAttach(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
config, _, _ := mustParse(t, tc.input)
|
||||
assert.Equal(t, config.AttachStdin, tc.expected.AttachStdin)
|
||||
|
@ -802,7 +801,6 @@ func TestParseRestartPolicy(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.input, func(t *testing.T) {
|
||||
_, hostConfig, _, err := parseRun([]string{"--restart=" + tc.input, "img", "cmd"})
|
||||
if tc.expectedErr != "" {
|
||||
|
|
|
@ -43,7 +43,6 @@ func TestNewPortCommandOutput(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
inspectFunc: func(string) (container.InspectResponse, error) {
|
||||
|
|
|
@ -58,7 +58,6 @@ func TestRestart(t *testing.T) {
|
|||
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var restarted []string
|
||||
mutex := new(sync.Mutex)
|
||||
|
|
|
@ -23,7 +23,6 @@ func TestRemoveForce(t *testing.T) {
|
|||
{name: "without force", args: []string{"nosuchcontainer", "mycontainer"}, expectedErr: "no such container"},
|
||||
{name: "with force", args: []string{"--force", "nosuchcontainer", "mycontainer"}, expectedErr: ""},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var removed []string
|
||||
mutex := new(sync.Mutex)
|
||||
|
|
|
@ -69,16 +69,16 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
|||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||
copts = addFlags(flags)
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
||||
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
||||
addCompletions(cmd, dockerCli)
|
||||
|
||||
flags.VisitAll(func(flag *pflag.Flag) {
|
||||
// Set a default completion function if none was set. We don't look
|
||||
// up if it does already have one set, because Cobra does this for
|
||||
// us, and returns an error (which we ignore for this reason).
|
||||
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,6 @@ func TestRunValidateFlags(t *testing.T) {
|
|||
expectedErr: "conflicting options: cannot specify both --attach and --detach",
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewRunCommand(test.NewFakeCli(&fakeClient{}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
@ -245,7 +244,6 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
|
@ -286,7 +284,6 @@ func TestRunContainerImagePullPolicyInvalid(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.PullPolicy, func(t *testing.T) {
|
||||
dockerCli := test.NewFakeCli(&fakeClient{})
|
||||
err := runRun(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package container
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -264,31 +265,40 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
|||
// so we unlikely hit this code in practice.
|
||||
daemonOSType = dockerCLI.ServerInfo().OSType
|
||||
}
|
||||
|
||||
// Buffer to store formatted stats text.
|
||||
// Once formatted, it will be printed in one write to avoid screen flickering.
|
||||
var statsTextBuffer bytes.Buffer
|
||||
|
||||
statsCtx := formatter.Context{
|
||||
Output: dockerCLI.Out(),
|
||||
Output: &statsTextBuffer,
|
||||
Format: NewStatsFormat(format, daemonOSType),
|
||||
}
|
||||
cleanScreen := func() {
|
||||
if !options.NoStream {
|
||||
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[2J")
|
||||
_, _ = fmt.Fprint(dockerCLI.Out(), "\033[H")
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
cleanScreen()
|
||||
var ccStats []StatsEntry
|
||||
cStats.mu.RLock()
|
||||
for _, c := range cStats.cs {
|
||||
ccStats = append(ccStats, c.GetStatistics())
|
||||
}
|
||||
cStats.mu.RUnlock()
|
||||
|
||||
if !options.NoStream {
|
||||
// Start by clearing the screen and moving the cursor to the top-left
|
||||
_, _ = fmt.Fprint(&statsTextBuffer, "\033[2J\033[H")
|
||||
}
|
||||
|
||||
if err = statsFormatWrite(statsCtx, ccStats, daemonOSType, !options.NoTrunc); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(dockerCLI.Out(), statsTextBuffer.String())
|
||||
|
||||
statsTextBuffer.Reset()
|
||||
|
||||
if len(cStats.cs) == 0 && !showAll {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -58,7 +58,6 @@ func TestStop(t *testing.T) {
|
|||
expectedErr: "conflicting options: cannot specify both --timeout and --time",
|
||||
},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var stopped []string
|
||||
mutex := new(sync.Mutex)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package command
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package context
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package context
|
||||
|
||||
|
@ -94,7 +94,6 @@ func TestCreate(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.options.Name, func(t *testing.T) {
|
||||
err := RunCreate(cli, &tc.options)
|
||||
if tc.expecterErr == "" {
|
||||
|
@ -164,25 +163,24 @@ func TestCreateFromContext(t *testing.T) {
|
|||
|
||||
cli.SetCurrentContext("dummy")
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli.ResetOutputBuffers()
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
From: "original",
|
||||
Name: c.name,
|
||||
Description: c.description,
|
||||
Docker: c.docker,
|
||||
Name: tc.name,
|
||||
Description: tc.description,
|
||||
Docker: tc.docker,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assertContextCreateLogging(t, cli, c.name)
|
||||
newContext, err := cli.ContextStore().GetMetadata(c.name)
|
||||
assertContextCreateLogging(t, cli, tc.name)
|
||||
newContext, err := cli.ContextStore().GetMetadata(tc.name)
|
||||
assert.NilError(t, err)
|
||||
newContextTyped, err := command.GetDockerContext(newContext)
|
||||
assert.NilError(t, err)
|
||||
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
|
||||
assert.Equal(t, newContextTyped.Description, tc.expectedDescription)
|
||||
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
||||
})
|
||||
}
|
||||
|
@ -219,23 +217,22 @@ func TestCreateFromCurrent(t *testing.T) {
|
|||
|
||||
cli.SetCurrentContext("original")
|
||||
|
||||
for _, c := range cases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli.ResetOutputBuffers()
|
||||
err := RunCreate(cli, &CreateOptions{
|
||||
Name: c.name,
|
||||
Description: c.description,
|
||||
Name: tc.name,
|
||||
Description: tc.description,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assertContextCreateLogging(t, cli, c.name)
|
||||
newContext, err := cli.ContextStore().GetMetadata(c.name)
|
||||
assertContextCreateLogging(t, cli, tc.name)
|
||||
newContext, err := cli.ContextStore().GetMetadata(tc.name)
|
||||
assert.NilError(t, err)
|
||||
newContextTyped, err := command.GetDockerContext(newContext)
|
||||
assert.NilError(t, err)
|
||||
dockerEndpoint, err := docker.EndpointFromContext(newContext)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, newContextTyped.Description, c.expectedDescription)
|
||||
assert.Equal(t, newContextTyped.Description, tc.expectedDescription)
|
||||
assert.Equal(t, dockerEndpoint.Host, "tcp://42.42.42.42:2375")
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package context
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package context
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package command
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package command
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package command
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
@ -346,7 +346,6 @@ size: 0B
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
@ -411,7 +410,6 @@ func TestContainerContextWriteWithNoContainers(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := ContainerWrite(tc.context, containers)
|
||||
assert.NilError(t, err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -106,7 +106,6 @@ Build Cache 0 0 0B 0B
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -304,7 +304,6 @@ image_id: imageID3
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
@ -365,7 +364,6 @@ func TestImageContextWriteWithNoImage(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := ImageWrite(tc.context, images)
|
||||
assert.NilError(t, err)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package formatter
|
||||
|
||||
|
@ -131,7 +131,6 @@ foobar_bar
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package idresolver
|
||||
|
||||
|
|
|
@ -255,7 +255,6 @@ imageID6 17 years ago /bin/bash echo 183MB
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
err := HistoryWrite(tc.context, true, histories)
|
||||
assert.NilError(t, err)
|
||||
|
|
|
@ -42,7 +42,6 @@ func TestNewHistoryCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
@ -109,7 +108,6 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Set to UTC timezone as timestamps in output are
|
||||
// printed in the current timezone
|
||||
|
|
|
@ -98,7 +98,6 @@ func TestNewImportCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package image
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ func TestNewInspectCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
@ -79,7 +78,6 @@ func TestNewInspectCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
imageInspectInvocationCount = 0
|
||||
cli := test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc})
|
||||
|
|
|
@ -35,7 +35,6 @@ func TestNewImagesCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
|
||||
cmd.SetOut(io.Discard)
|
||||
|
@ -83,7 +82,6 @@ func TestNewImagesCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc})
|
||||
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
|
||||
|
|
|
@ -52,7 +52,6 @@ func TestNewLoadCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
||||
cli.In().SetIsTerminal(tc.isTerminalIn)
|
||||
|
@ -116,7 +115,6 @@ func TestNewLoadCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
|
||||
cmd := NewLoadCommand(cli)
|
||||
|
|
|
@ -39,7 +39,6 @@ func TestNewPruneCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
|
||||
imagesPruneFunc: tc.imagesPruneFunc,
|
||||
|
@ -98,7 +97,6 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imagesPruneFunc: tc.imagesPruneFunc})
|
||||
// when prompted, answer "Y" to confirm the prune.
|
||||
|
|
|
@ -38,7 +38,6 @@ func TestNewPullCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := NewPullCommand(cli)
|
||||
|
@ -73,7 +72,6 @@ func TestNewPullCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
|
@ -119,7 +117,6 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package image
|
||||
|
||||
|
|
|
@ -38,7 +38,6 @@ func TestNewPushCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
|
||||
cmd := NewPushCommand(cli)
|
||||
|
@ -68,7 +67,6 @@ func TestNewPushCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePushFunc: func(ref string, options image.PushOptions) (io.ReadCloser, error) {
|
||||
|
|
|
@ -62,7 +62,6 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageRemoveFunc: tc.imageRemoveFunc,
|
||||
|
@ -121,7 +120,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||
cmd := NewRemoveCommand(cli)
|
||||
|
|
|
@ -59,7 +59,6 @@ func TestNewSaveCommandErrors(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
|
||||
cli.Out().SetIsTerminal(tc.isTerminal)
|
||||
|
@ -113,7 +112,6 @@ func TestNewSaveCommandSuccess(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(strings.Join(tc.args, " "), func(t *testing.T) {
|
||||
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageSaveFunc: tc.imageSaveFunc,
|
||||
|
|
|
@ -56,7 +56,6 @@ func runTree(ctx context.Context, dockerCLI command.Cli, opts treeOptions) error
|
|||
continue
|
||||
}
|
||||
|
||||
im := im
|
||||
sub := subImage{
|
||||
Platform: platforms.Format(im.ImageData.Platform),
|
||||
Available: im.Available,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package inspect
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ func TestManifestCreateErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.expectedError, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(nil)
|
||||
cmd := newCreateListCommand(cli)
|
||||
|
|
|
@ -218,7 +218,6 @@ func TestNetworkCreateIPv6(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkCreateFunc: func(ctx context.Context, name string, createBody network.CreateOptions) (network.CreateResponse, error) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package network
|
||||
|
||||
|
@ -161,7 +161,6 @@ foobar_bar 2017-01-01 00:00:00 +0000 UTC
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package network
|
||||
|
||||
|
|
|
@ -83,7 +83,6 @@ func TestNetworkList(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{networkListFunc: tc.networkListFunc})
|
||||
cmd := newListCommand(cli)
|
||||
|
|
|
@ -63,7 +63,6 @@ func TestNetworkRemoveForce(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
fakeCli := test.NewFakeCli(&fakeClient{
|
||||
networkRemoveFunc: func(ctx context.Context, networkID string) error {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package node
|
||||
|
||||
|
@ -202,7 +202,6 @@ foobar_boo Unknown
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package node
|
||||
|
||||
|
|
|
@ -106,7 +106,6 @@ func TestNodeInspectPretty(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
nodeInspectFunc: tc.nodeInspectFunc,
|
||||
|
|
|
@ -134,7 +134,6 @@ func TestNodePs(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
infoFunc: tc.infoFunc,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package plugin
|
||||
|
||||
|
@ -131,7 +131,6 @@ foobar_bar
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package plugin
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@ func TestInspectErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
||||
cmd := newInspectCommand(cli)
|
||||
|
@ -138,7 +137,6 @@ func TestInspect(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginInspectFunc: tc.inspectFunc})
|
||||
cmd := newInspectCommand(cli)
|
||||
|
|
|
@ -54,7 +54,6 @@ func TestInstallErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
||||
cmd := newInstallCommand(cli)
|
||||
|
@ -94,7 +93,6 @@ func TestInstallContentTrustErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
|
@ -138,7 +136,6 @@ func TestInstall(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginInstallFunc: tc.installFunc})
|
||||
cmd := newInstallCommand(cli)
|
||||
|
|
|
@ -46,7 +46,6 @@ func TestListErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
||||
cmd := newListCommand(cli)
|
||||
|
@ -166,7 +165,6 @@ func TestList(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{pluginListFunc: tc.listFunc})
|
||||
cmd := newListCommand(cli)
|
||||
|
|
|
@ -203,7 +203,6 @@ result2 5
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
err := SearchWrite(formatter.Context{Format: tc.format, Output: &out}, results)
|
||||
|
|
|
@ -61,7 +61,6 @@ id_rsa
|
|||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package secret
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ func TestSecretInspectWithoutFormat(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
secretInspectFunc: tc.secretInspectFunc,
|
||||
|
@ -132,7 +131,6 @@ func TestSecretInspectWithFormat(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
secretInspectFunc: tc.secretInspectFunc,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package service
|
||||
|
||||
|
@ -223,7 +223,6 @@ zarp2
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package service
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package service
|
||||
|
||||
|
|
|
@ -168,7 +168,6 @@ func TestServiceListServiceStatus(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range matrix {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
if tc.cluster == nil {
|
||||
tc.cluster = generateCluster(t, tc.opts)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package service
|
||||
|
||||
|
|
|
@ -50,7 +50,6 @@ func TestCredentialSpecOpt(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var cs credentialSpecOpt
|
||||
|
||||
|
|
|
@ -91,7 +91,6 @@ func TestRollbackWithErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := newRollbackCommand(
|
||||
test.NewFakeCli(&fakeClient{
|
||||
|
|
|
@ -1058,7 +1058,6 @@ func updatePorts(flags *pflag.FlagSet, portConfig *[]swarm.PortConfig) error {
|
|||
|
||||
// Build the current list of portConfig
|
||||
for _, entry := range *portConfig {
|
||||
entry := entry
|
||||
if _, ok := portSet[portConfigToString(&entry)]; !ok {
|
||||
portSet[portConfigToString(&entry)] = entry
|
||||
}
|
||||
|
@ -1086,7 +1085,6 @@ portLoop:
|
|||
ports := flags.Lookup(flagPublishAdd).Value.(*opts.PortOpt).Value()
|
||||
|
||||
for _, port := range ports {
|
||||
port := port
|
||||
if _, ok := portSet[portConfigToString(&port)]; ok {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -1690,7 +1690,6 @@ func TestUpdateUlimits(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
svc := swarm.ServiceSpec{
|
||||
TaskTemplate: swarm.TaskSpec{
|
||||
|
|
|
@ -51,7 +51,6 @@ bar
|
|||
{Name: "bar", Services: 1},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(string(tc.context.Format), func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
tc.context.Output = &out
|
||||
|
|
|
@ -48,7 +48,6 @@ func TestListErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.expectedError, func(t *testing.T) {
|
||||
cmd := newListCommand(test.NewFakeCli(&fakeClient{
|
||||
serviceListFunc: tc.serviceListFunc,
|
||||
|
@ -104,7 +103,6 @@ func TestStackList(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
var services []swarm.Service
|
||||
for _, name := range tc.serviceNames {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||
//go:build go1.21
|
||||
//go:build go1.22
|
||||
|
||||
package loader
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue