From 140dc519d3e2a17a5b12cb50d8f0ae90d63fbe6c Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Mon, 26 Sep 2022 19:21:45 +0200 Subject: [PATCH] cli: add shell completion function (#9269) Integrates PR #9462 with additional fixes/changes. Additional changes will be required to utilize this. Co-authored-by: Nicolas De Loof Signed-off-by: Ulysses Souza --- cmd/compatibility/convert.go | 11 +++++++++++ cmd/compose/build.go | 2 +- cmd/compose/completion.go | 23 +++++++++++++++++++++-- cmd/compose/compose.go | 11 +++++++++++ cmd/compose/convert.go | 2 +- cmd/compose/cp.go | 2 +- cmd/compose/create.go | 2 +- cmd/compose/events.go | 2 +- cmd/compose/exec.go | 2 +- cmd/compose/images.go | 2 +- cmd/compose/kill.go | 2 +- cmd/compose/logs.go | 2 +- cmd/compose/pause.go | 4 ++-- cmd/compose/port.go | 2 +- cmd/compose/ps.go | 2 +- cmd/compose/pull.go | 2 +- cmd/compose/push.go | 2 +- cmd/compose/remove.go | 2 +- cmd/compose/restart.go | 2 +- cmd/compose/run.go | 2 +- cmd/compose/start.go | 2 +- cmd/compose/stop.go | 2 +- cmd/compose/top.go | 2 +- cmd/compose/up.go | 2 +- cmd/main.go | 5 ++--- 25 files changed, 67 insertions(+), 27 deletions(-) diff --git a/cmd/compatibility/convert.go b/cmd/compatibility/convert.go index 9bef94bf5..fb6e9a83d 100644 --- a/cmd/compatibility/convert.go +++ b/cmd/compatibility/convert.go @@ -23,6 +23,13 @@ import ( "github.com/docker/compose/v2/cmd/compose" ) +func getCompletionCommands() []string { + return []string{ + "__complete", + "__completeNoDesc", + } +} + func getBoolFlags() []string { return []string{ "--debug", "-D", @@ -50,6 +57,10 @@ func Convert(args []string) []string { l := len(args) for i := 0; i < l; i++ { arg := args[i] + if contains(getCompletionCommands(), arg) { + command = append([]string{arg}, command...) + continue + } if len(arg) > 0 && arg[0] != '-' { // not a top-level flag anymore, keep the rest of the command unmodified if arg == compose.PluginName { diff --git a/cmd/compose/build.go b/cmd/compose/build.go index b19b1b970..a3b35ff85 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -102,7 +102,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command { } return runBuild(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT") cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.") diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go index 891539b6b..ea4120f53 100644 --- a/cmd/compose/completion.go +++ b/cmd/compose/completion.go @@ -19,6 +19,7 @@ package compose import ( "strings" + "github.com/docker/compose/v2/pkg/api" "github.com/spf13/cobra" ) @@ -27,11 +28,11 @@ type validArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]s func noCompletion() validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return nil, cobra.ShellCompDirectiveNoFileComp + return []string{}, cobra.ShellCompDirectiveNoSpace } } -func serviceCompletion(p *projectOptions) validArgsFn { +func completeServiceNames(p *projectOptions) validArgsFn { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { project, err := p.toProject(nil) if err != nil { @@ -46,3 +47,21 @@ func serviceCompletion(p *projectOptions) validArgsFn { return serviceNames, cobra.ShellCompDirectiveNoFileComp } } + +func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + list, err := backend.List(cmd.Context(), api.ListOptions{ + All: true, + }) + if err != nil { + return nil, cobra.ShellCompDirectiveError + } + var values []string + for _, stack := range list { + if strings.HasPrefix(stack.Name, toComplete) { + values = append(values, stack.Name) + } + } + return values, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 350eb5e88..f10865521 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -358,6 +358,17 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { ) c.Flags().SetInterspersed(false) opts.addProjectFlags(c.Flags()) + c.RegisterFlagCompletionFunc( //nolint:errcheck + "project-name", + completeProjectNames(backend), + ) + c.RegisterFlagCompletionFunc( //nolint:errcheck + "file", + func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"yaml", "yml"}, cobra.ShellCompDirectiveFilterFileExt + }, + ) + c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`) c.Flags().BoolVarP(&version, "version", "v", false, "Show the Docker Compose version information") c.Flags().MarkHidden("version") //nolint:errcheck diff --git a/cmd/compose/convert.go b/cmd/compose/convert.go index b1adecc98..adc61f317 100644 --- a/cmd/compose/convert.go +++ b/cmd/compose/convert.go @@ -93,7 +93,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command { return runConvert(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]") diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go index 804920754..d44cd3f0d 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -60,7 +60,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command { opts.destination = args[1] return runCopy(ctx, backend, opts) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := copyCmd.Flags() diff --git a/cmd/compose/create.go b/cmd/compose/create.go index a1b8d0fc3..66f0e22b8 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -70,7 +70,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command { QuietPull: false, }) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") diff --git a/cmd/compose/events.go b/cmd/compose/events.go index f50605b4f..39609ad36 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -43,7 +43,7 @@ func eventsCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runEvents(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects") diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index 89b30ee89..c58cfebfc 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -61,7 +61,7 @@ func execCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) RunE: Adapt(func(ctx context.Context, args []string) error { return runExec(ctx, backend, opts) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.") diff --git a/cmd/compose/images.go b/cmd/compose/images.go index 208b8c95f..469d0b103 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -48,7 +48,7 @@ func imagesCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runImages(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs") return imgCmd diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index d1fb16ff7..68c0f35a4 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -42,7 +42,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runKill(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index a9fd7255a..767fa8643 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -49,7 +49,7 @@ func logsCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runLogs(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := logsCmd.Flags() flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.") diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go index 9afbcaec4..6072c0bf4 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -38,7 +38,7 @@ func pauseCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runPause(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } return cmd } @@ -69,7 +69,7 @@ func unpauseCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runUnPause(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } return cmd } diff --git a/cmd/compose/port.go b/cmd/compose/port.go index 7c539210d..59e61f491 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -52,7 +52,7 @@ func portCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runPort(ctx, backend, opts, args[0]) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp") cmd.Flags().IntVar(&opts.index, "index", 1, "index of the container if service has multiple replicas") diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index 17581aec2..ba90039fb 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -78,7 +78,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runPs(ctx, backend, args, opts) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := psCmd.Flags() flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]") diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 32897498f..48faa55dd 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -54,7 +54,7 @@ func pullCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runPull(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information") diff --git a/cmd/compose/push.go b/cmd/compose/push.go index ef4a53d0c..d14d2bd67 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -41,7 +41,7 @@ func pushCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runPush(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } pushCmd.Flags().BoolVar(&opts.Ignorefailures, "ignore-push-failures", false, "Push what it can and ignores images with push failures") diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go index 2c1152494..afc54d8d5 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -46,7 +46,7 @@ Any data which is not in a volume will be lost.`, RunE: Adapt(func(ctx context.Context, args []string) error { return runRemove(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } f := cmd.Flags() f.BoolVarP(&opts.force, "force", "f", false, "Don't ask to confirm removal") diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index 2bb8c8f3e..7e2b61559 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -40,7 +40,7 @@ func restartCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runRestart(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := restartCmd.Flags() flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds") diff --git a/cmd/compose/run.go b/cmd/compose/run.go index bf81ef52d..9922d792c 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -143,7 +143,7 @@ func runCommand(p *projectOptions, dockerCli command.Cli, backend api.Service) * opts.ignoreOrphans = strings.ToLower(ignore) == "true" return runRun(ctx, backend, project, opts) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() flags.BoolVarP(&opts.Detach, "detach", "d", false, "Run container in background and print container ID") diff --git a/cmd/compose/start.go b/cmd/compose/start.go index 7b7a96265..7adb3331c 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -37,7 +37,7 @@ func startCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runStart(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } return startCmd } diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index 484bed824..8ad8e7f07 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -44,7 +44,7 @@ func stopCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runStop(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := cmd.Flags() flags.IntVarP(&opts.timeout, "timeout", "t", 10, "Specify a shutdown timeout in seconds") diff --git a/cmd/compose/top.go b/cmd/compose/top.go index 60f3a1fac..70c3f0175 100644 --- a/cmd/compose/top.go +++ b/cmd/compose/top.go @@ -44,7 +44,7 @@ func topCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runTop(ctx, backend, opts, args) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } return topCmd } diff --git a/cmd/compose/up.go b/cmd/compose/up.go index ca6b57f51..91261d1ee 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -109,7 +109,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command { } return runUp(ctx, backend, create, up, project, services) }), - ValidArgsFunction: serviceCompletion(p), + ValidArgsFunction: completeServiceNames(p), } flags := upCmd.Flags() flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background") diff --git a/cmd/main.go b/cmd/main.go index 2325d975e..dfa626260 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,14 +34,13 @@ import ( func pluginMain() { plugin.Run(func(dockerCli command.Cli) *cobra.Command { - lazyInit := api.NewServiceProxy() - cmd := commands.RootCommand(dockerCli, lazyInit) + serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli)) + cmd := commands.RootCommand(dockerCli, serviceProxy) originalPreRun := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { if err := plugin.PersistentPreRunE(cmd, args); err != nil { return err } - lazyInit.WithService(compose.NewComposeService(dockerCli)) if originalPreRun != nil { return originalPreRun(cmd, args) }