Skip to content

Commit

Permalink
Merge pull request #5261 from thaJeztah/27.1_backport_completion_enha…
Browse files Browse the repository at this point in the history
…ncements

[27.0 backport] assorted fixes and enhancements for shell-completion
  • Loading branch information
thaJeztah authored Jul 19, 2024
2 parents 7e972d2 + 66ec714 commit 33bf62c
Show file tree
Hide file tree
Showing 18 changed files with 203 additions and 45 deletions.
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ mod-outdated: ## check outdated dependencies
authors: ## generate AUTHORS file from git history
scripts/docs/generate-authors.sh

.PHONY: completion
completion: binary
completion: /etc/bash_completion.d/docker
completion: ## generate and install the completion scripts

.PHONY: /etc/bash_completion.d/docker
/etc/bash_completion.d/docker: ## generate and install the bash-completion script
mkdir -p /etc/bash_completion.d
docker completion bash > /etc/bash_completion.d/docker

.PHONY: manpages
manpages: ## generate man pages from go source and markdown
scripts/docs/generate-man.sh
Expand Down
36 changes: 36 additions & 0 deletions cli/command/completion/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package completion

import (
"os"
"strings"

"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -106,6 +107,41 @@ func NetworkNames(dockerCLI APIClientProvider) ValidArgsFn {
}
}

// EnvVarNames offers completion for environment-variable names. This
// completion can be used for "--env" and "--build-arg" flags, which
// allow obtaining the value of the given environment-variable if present
// in the local environment, so we only should complete the names of the
// environment variables, and not their value. This also prevents the
// completion script from printing values of environment variables
// containing sensitive values.
//
// For example;
//
// export MY_VAR=hello
// docker run --rm --env MY_VAR alpine printenv MY_VAR
// hello
func EnvVarNames(_ *cobra.Command, _ []string, _ string) (names []string, _ cobra.ShellCompDirective) {
envs := os.Environ()
names = make([]string, 0, len(envs))
for _, env := range envs {
name, _, _ := strings.Cut(env, "=")
names = append(names, name)
}
return names, cobra.ShellCompDirectiveNoFileComp
}

// FromList offers completion for the given list of options.
func FromList(options ...string) ValidArgsFn {
return cobra.FixedCompletions(options, cobra.ShellCompDirectiveNoFileComp)
}

// FileNames is a convenience function to use [cobra.ShellCompDirectiveDefault],
// which indicates to let the shell perform its default behavior after
// completions have been provided.
func FileNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
}

// NoComplete is used for commands where there's no relevant completion
func NoComplete(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveNoFileComp
Expand Down
94 changes: 94 additions & 0 deletions cli/command/container/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package container

import (
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/container"
"github.com/moby/sys/signal"
"github.com/spf13/cobra"
)

// allLinuxCapabilities is a list of all known Linux capabilities.
//
// This list was based on the containerd pkg/cap package;
// https://github.com/containerd/containerd/blob/v1.7.19/pkg/cap/cap_linux.go#L133-L181
//
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
var allLinuxCapabilities = []string{
"ALL", // magic value for "all capabilities"

// caps35 is the caps of kernel 3.5 (37 entries)
"CAP_CHOWN", // 2.2
"CAP_DAC_OVERRIDE", // 2.2
"CAP_DAC_READ_SEARCH", // 2.2
"CAP_FOWNER", // 2.2
"CAP_FSETID", // 2.2
"CAP_KILL", // 2.2
"CAP_SETGID", // 2.2
"CAP_SETUID", // 2.2
"CAP_SETPCAP", // 2.2
"CAP_LINUX_IMMUTABLE", // 2.2
"CAP_NET_BIND_SERVICE", // 2.2
"CAP_NET_BROADCAST", // 2.2
"CAP_NET_ADMIN", // 2.2
"CAP_NET_RAW", // 2.2
"CAP_IPC_LOCK", // 2.2
"CAP_IPC_OWNER", // 2.2
"CAP_SYS_MODULE", // 2.2
"CAP_SYS_RAWIO", // 2.2
"CAP_SYS_CHROOT", // 2.2
"CAP_SYS_PTRACE", // 2.2
"CAP_SYS_PACCT", // 2.2
"CAP_SYS_ADMIN", // 2.2
"CAP_SYS_BOOT", // 2.2
"CAP_SYS_NICE", // 2.2
"CAP_SYS_RESOURCE", // 2.2
"CAP_SYS_TIME", // 2.2
"CAP_SYS_TTY_CONFIG", // 2.2
"CAP_MKNOD", // 2.4
"CAP_LEASE", // 2.4
"CAP_AUDIT_WRITE", // 2.6.11
"CAP_AUDIT_CONTROL", // 2.6.11
"CAP_SETFCAP", // 2.6.24
"CAP_MAC_OVERRIDE", // 2.6.25
"CAP_MAC_ADMIN", // 2.6.25
"CAP_SYSLOG", // 2.6.37
"CAP_WAKE_ALARM", // 3.0
"CAP_BLOCK_SUSPEND", // 3.5

// caps316 is the caps of kernel 3.16 (38 entries)
"CAP_AUDIT_READ",

// caps58 is the caps of kernel 5.8 (40 entries)
"CAP_PERFMON",
"CAP_BPF",

// caps59 is the caps of kernel 5.9 (41 entries)
"CAP_CHECKPOINT_RESTORE",
}

// 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")
var restartPolicies = []string{
string(container.RestartPolicyDisabled),
string(container.RestartPolicyAlways),
string(container.RestartPolicyOnFailure),
string(container.RestartPolicyUnlessStopped),
}

func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
return completion.FromList(allLinuxCapabilities...)(cmd, args, toComplete)
}

func completeRestartPolicies(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
return completion.FromList(restartPolicies...)(cmd, args, toComplete)
}

func completeSignals(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
// TODO(thaJeztah): do we want to provide the full list here, or a subset?
signalNames := make([]string, 0, len(signal.SignalMap))
for k := range signal.SignalMap {
signalNames = append(signalNames, k)
}
return completion.FromList(signalNames...)(cmd, args, toComplete)
}
10 changes: 10 additions & 0 deletions cli/command/container/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
command.AddPlatformFlag(flags, &options.platform)
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("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
return cmd
}

Expand Down
9 changes: 2 additions & 7 deletions cli/command/container/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"os"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
Expand Down Expand Up @@ -79,12 +78,8 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&options.Workdir, "workdir", "w", "", "Working directory inside the container")
flags.SetAnnotation("workdir", "version", []string{"1.35"})

_ = cmd.RegisterFlagCompletionFunc("env", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
})
_ = cmd.RegisterFlagCompletionFunc("env-file", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault // _filedir
})
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)

return cmd
}
Expand Down
5 changes: 4 additions & 1 deletion cli/command/container/kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {

flags := cmd.Flags()
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")

_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)

return cmd
}

Expand All @@ -50,7 +53,7 @@ func runKill(ctx context.Context, dockerCli command.Cli, opts *killOptions) erro
if err := <-errChan; err != nil {
errs = append(errs, err.Error())
} else {
fmt.Fprintln(dockerCli.Out(), name)
_, _ = fmt.Fprintln(dockerCli.Out(), name)
}
}
if len(errs) > 0 {
Expand Down
3 changes: 3 additions & 0 deletions cli/command/container/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")

_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)

return cmd
}

Expand Down
26 changes: 9 additions & 17 deletions cli/command/container/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"fmt"
"io"
"os"
"strings"
"syscall"

Expand Down Expand Up @@ -70,22 +69,15 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
copts = addFlags(flags)

cmd.RegisterFlagCompletionFunc(
"env",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return os.Environ(), cobra.ShellCompDirectiveNoFileComp
},
)
cmd.RegisterFlagCompletionFunc(
"env-file",
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return nil, cobra.ShellCompDirectiveDefault
},
)
cmd.RegisterFlagCompletionFunc(
"network",
completion.NetworkNames(dockerCli),
)
_ = 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("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
return cmd
}

Expand Down
3 changes: 3 additions & 0 deletions cli/command/container/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags()
flags.StringVarP(&opts.signal, "signal", "s", "", "Signal to send to the container")
flags.IntVarP(&opts.timeout, "time", "t", 0, "Seconds to wait before killing the container")

_ = cmd.RegisterFlagCompletionFunc("signal", completeSignals)

return cmd
}

Expand Down
2 changes: 2 additions & 0 deletions cli/command/container/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
flags.Var(&options.cpus, "cpus", "Number of CPUs")
flags.SetAnnotation("cpus", "version", []string{"1.29"})

_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)

return cmd
}

Expand Down
2 changes: 2 additions & 0 deletions cli/command/image/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/formatter"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/spf13/cobra"
Expand All @@ -31,6 +32,7 @@ func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
opts.image = args[0]
return runHistory(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
Annotations: map[string]string{
"aliases": "docker image history, docker history",
},
Expand Down
2 changes: 2 additions & 0 deletions cli/command/image/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/cli/cli/command/inspect"
flagsHelper "github.com/docker/cli/cli/flags"
"github.com/spf13/cobra"
Expand All @@ -30,6 +31,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
opts.refs = args
return runInspect(cmd.Context(), dockerCli, opts)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
}

flags := cmd.Flags()
Expand Down
2 changes: 2 additions & 0 deletions cli/command/image/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/completion"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
Expand All @@ -29,6 +30,7 @@ func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(cmd.Context(), dockerCli, opts, args)
},
ValidArgsFunction: completion.ImageNames(dockerCli),
Annotations: map[string]string{
"aliases": "docker image rm, docker image remove, docker rmi",
},
Expand Down
3 changes: 3 additions & 0 deletions cli/context/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ func (s *ContextStore) List() ([]Metadata, error) {

// Names return Metadata names for a Lister
func Names(s Lister) ([]string, error) {
if s == nil {
return nil, errors.New("nil lister")
}
list, err := s.List()
if err != nil {
return nil, err
Expand Down
6 changes: 6 additions & 0 deletions cli/context/store/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,9 @@ func TestCorruptMetadata(t *testing.T) {
_, err = s.GetMetadata("source")
assert.ErrorContains(t, err, fmt.Sprintf("parsing %s: unexpected end of JSON input", contextFile))
}

func TestNames(t *testing.T) {
names, err := Names(nil)
assert.Check(t, is.Error(err, "nil lister"))
assert.Check(t, is.Len(names, 0))
}
Loading

0 comments on commit 33bf62c

Please sign in to comment.