Compare commits

...

23 Commits

Author SHA1 Message Date
Alano Terblanche b56e449029
Merge eea706f6f4 into 9861ce90fd 2024-11-15 11:26:28 +01:00
Sebastiaan van Stijn 9861ce90fd
Merge pull request #5621 from thaJeztah/more_go_build_tags
cli/command/container: fix missing go:build tag
2024-11-15 11:24:51 +01:00
Sebastiaan van Stijn d1d5353269
cli/command/container: fix missing go:build tag
make shell
    make -C ./internal/gocompat/

    GO111MODULE=on go test -v
    # github.com/docker/cli/cli/command/container
    ../../cli/command/container/completion.go:37:28: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/command/container/completion.go:82:25: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    ../../cli/command/container/completion.go:92:27: implicit function instantiation requires go1.18 or later (-lang was set to go1.16; check go.mod)
    FAIL	gocompat [build failed]

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-11-15 11:06:20 +01:00
Sebastiaan van Stijn 4adbb18f1c
Merge pull request #5580 from albers/container-completions
Improve Cobra completions for `run` and `create`
2024-11-14 16:58:54 +01:00
Alano Terblanche eea706f6f4
chore: update docker client
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-11-14 09:44:36 +01:00
Harald Albers 06260e68f3 Handle null completions with a default callback
Credits to thaJeztah

Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 4525fe37b4 Add completion for `--volume-driver`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers db0ed1e216 Add completion for `--cgroupns`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 2915749279 Add completion for `--uts`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 3a2503fa43 Add completion for --log-driver` and --log-opt`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 9a9ae231a9 Add completion for `--security-opt`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 5f7c43e5e6 Add completion for `--detach-keys`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 3292afe6e6 Add completion for `--userns`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 5d709a8d9f Add completion for `--ulimit`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers 2d89339b34 Add completion for `--storage-opt`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers ac7bde6f64 Add completion for `--pid`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:55:59 +00:00
Harald Albers e513454244 Add completion for `--link`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:35:34 +00:00
Harald Albers c555327f0b Add completion for `--ipc`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:35:34 +00:00
Harald Albers b598ec8cdb Add completion for `--attach`
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:35:34 +00:00
Harald Albers 761d76750c Share the container completions
Signed-off-by: Harald Albers <github@albersweb.de>
2024-11-08 15:35:34 +00:00
Alano Terblanche 5667a952c5
feat: use engine API instead of Hub directly
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-10-18 14:42:47 +02:00
Alano Terblanche 5760a3d201
feat: run with hub completion
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-10-11 10:27:41 +02:00
Alano Terblanche f4c164d9b8
feat: pull completion using hub
Signed-off-by: Alano Terblanche <18033717+Benehiko@users.noreply.github.com>
2024-10-11 09:57:32 +02:00
6 changed files with 496 additions and 24 deletions

View File

@ -1,3 +1,7 @@
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
//go:build go1.22
// +build go1.22
package container package container
import ( import (
@ -44,6 +48,65 @@ var allLinuxCapabilities = sync.OnceValue(func() []string {
return out 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.. // 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") // TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
@ -54,6 +117,207 @@ var restartPolicies = []string{
string(container.RestartPolicyUnlessStopped), 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) { func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete) return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete)
} }

View File

@ -4,6 +4,9 @@ import (
"strings" "strings"
"testing" "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/moby/sys/signal"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gotest.tools/v3/assert" "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) { func TestCompleteRestartPolicies(t *testing.T) {
values, directives := completeRestartPolicies(nil, nil, "") values, directives := completeRestartPolicies(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") 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)) 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) { func TestCompleteSignals(t *testing.T) {
values, directives := completeSignals(nil, nil, "") values, directives := completeSignals(nil, nil, "")
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion") assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")

View File

@ -78,16 +78,15 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
copts = addFlags(flags) copts = addFlags(flags)
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames) addCompletions(cmd, dockerCli)
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames) flags.VisitAll(func(flag *pflag.Flag) {
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) // Set a default completion function if none was set. We don't look
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) // up if it does already have one set, because Cobra does this for
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) // us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) })
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
return cmd return cmd
} }

View File

@ -12,6 +12,7 @@ import (
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/opts" "github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/hub"
"github.com/moby/sys/signal" "github.com/moby/sys/signal"
"github.com/moby/term" "github.com/moby/term"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -37,13 +38,74 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
Short: "Create and run a new container from an image", Short: "Create and run a new container from an image",
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
copts.Image = args[0] replacer := strings.NewReplacer("(local)", "", "(remote)", "")
copts.Image = replacer.Replace(args[0])
if len(args) > 1 { if len(args) > 1 {
copts.Args = args[1:] copts.Args = args[1:]
} }
return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts) return runRun(cmd.Context(), dockerCli, cmd.Flags(), &options, copts)
}, },
ValidArgsFunction: completion.ImageNames(dockerCli), ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
unique := map[string]struct{}{}
localImages, shellComp := completion.ImageNames(dockerCli)(cmd, args, toComplete)
var all []string
if shellComp != cobra.ShellCompDirectiveError {
all = make([]string, 0, len(localImages))
for _, img := range localImages {
unique[img] = struct{}{}
all = append(all, img+"\tlocal")
}
}
if image, tag, found := strings.Cut(toComplete, ":"); found {
remoteTags, err := dockerCli.Client().HubImageTags(cmd.Context(), image, hub.ImageOptions{
Name: tag,
Ordering: "last_updated",
Page: 0,
PageSize: 25,
})
if err == nil {
if len(all) == 0 {
all = make([]string, 0, len(remoteTags.Results))
}
for _, tag := range remoteTags.Results {
fullName := image + ":" + tag.Name
if _, ok := unique[fullName]; !ok {
all = append(all, fullName+"\tremote")
}
}
}
return all, cobra.ShellCompDirectiveKeepOrder | cobra.ShellCompDirectiveNoFileComp
}
remoteImages, err := dockerCli.Client().HubImageSearch(cmd.Context(), toComplete, hub.SearchOptions{
From: 0,
Size: 25,
Type: hub.SearchTypeImage,
Order: hub.SearchOrderDesc,
Official: true,
Source: hub.SearchSourceStore,
OpenSource: true,
ExtensionReviewed: true,
})
if err == nil {
if len(all) == 0 {
all = make([]string, 0, len(remoteImages.Results))
}
for _, img := range remoteImages.Results {
if _, ok := unique[img.Name]; !ok {
all = append(all, img.Name+"\tremote")
}
}
}
return all, cobra.ShellCompDirectiveKeepOrder | cobra.ShellCompDirectiveNoFileComp
},
Annotations: map[string]string{ Annotations: map[string]string{
"category-top": "1", "category-top": "1",
"aliases": "docker container run, docker run", "aliases": "docker container run, docker run",
@ -69,16 +131,16 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
copts = addFlags(flags) copts = addFlags(flags)
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames) _ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames) addCompletions(cmd, dockerCli)
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames) flags.VisitAll(func(flag *pflag.Flag) {
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli)) // Set a default completion function if none was set. We don't look
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms) // up if it does already have one set, because Cobra does this for
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever)) // us, and returns an error (which we ignore for this reason).
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies) _ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals) })
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
return cmd return cmd
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
"github.com/docker/docker/api/types/hub"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -39,7 +40,55 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
"category-top": "5", "category-top": "5",
"aliases": "docker image pull, docker pull", "aliases": "docker image pull, docker pull",
}, },
ValidArgsFunction: completion.NoComplete, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) > 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
}
if image, tag, found := strings.Cut(toComplete, ":"); found {
remoteTags, err := dockerCli.Client().HubImageTags(cmd.Context(), image, hub.ImageOptions{
Name: tag,
Ordering: "last_updated",
Page: 0,
PageSize: 25,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
images := make([]string, 0, len(remoteTags.Results))
for _, tag := range remoteTags.Results {
fullName := image + ":" + tag.Name
images = append(images, fullName+"\t"+tag.LastUpdated.String())
}
return images, cobra.ShellCompDirectiveKeepOrder | cobra.ShellCompDirectiveNoFileComp
}
remoteImages, err := dockerCli.Client().HubImageSearch(cmd.Context(), toComplete, hub.SearchOptions{
From: 0,
Size: 25,
Type: hub.SearchTypeImage,
Order: hub.SearchOrderDesc,
Official: true,
Source: hub.SearchSourceStore,
OpenSource: true,
ExtensionReviewed: true,
})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
images := make([]string, 0, len(remoteImages.Results))
for _, img := range remoteImages.Results {
categories := make([]string, 0, len(img.Categories))
for _, cat := range img.Categories {
categories = append(categories, cat.Name)
}
images = append(images, img.Name+"\t"+strings.Join(categories, ", "))
}
return images, cobra.ShellCompDirectiveKeepOrder | cobra.ShellCompDirectiveNoFileComp
},
} }
flags := cmd.Flags() flags := cmd.Flags()

View File

@ -92,7 +92,7 @@ func newDockerCommand(dockerCli *command.DockerCli) *cli.TopLevelCommand {
CompletionOptions: cobra.CompletionOptions{ CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: false, DisableDefaultCmd: false,
HiddenDefaultCmd: true, HiddenDefaultCmd: true,
DisableDescriptions: true, DisableDescriptions: false,
}, },
} }
cmd.SetIn(dockerCli.In()) cmd.SetIn(dockerCli.In())