diff --git a/cli/main.go b/cli/main.go index e98568d46..4a4dd2ca0 100644 --- a/cli/main.go +++ b/cli/main.go @@ -68,6 +68,9 @@ var ( "serve": {}, "version": {}, "backend-metadata": {}, + // Special hidden commands used by cobra for completion + "__complete": {}, + "__completeNoDesc": {}, } unknownCommandRegexp = regexp.MustCompile(`unknown docker command: "([^"]*)"`) ) @@ -167,7 +170,7 @@ func main() { }) // populate the opts with the global flags - flags.Parse(os.Args[1:]) //nolint: errcheck + flags.Parse(os.Args[1:]) // nolint: errcheck level, err := logrus.ParseLevel(opts.LogLevel) if err != nil { @@ -223,18 +226,16 @@ func main() { volume.Command(ctype), ) - if ctype != store.DefaultContextType { - // On default context, "compose" is implemented by CLI Plugin - proxy := api.NewServiceProxy().WithService(service.ComposeService()) - command := compose2.RootCommand(ctype, proxy) + // On default context, "compose" is implemented by CLI Plugin + proxy := api.NewServiceProxy().WithService(service.ComposeService()) + command := compose2.RootCommand(ctype, proxy) - if ctype == store.AciContextType { - customizeCliForACI(command, proxy) - } - - root.AddCommand(command) + if ctype == store.AciContextType { + customizeCliForACI(command, proxy) } + root.AddCommand(command) + if err = root.ExecuteContext(ctx); err != nil { handleError(ctx, err, ctype, currentContext, cc, root) } diff --git a/cmd/compose/build.go b/cmd/compose/build.go index bd1a41196..aefe3d0fd 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -61,6 +61,7 @@ func buildCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runBuild(ctx, backend, opts, args) }), + ValidArgsFunction: serviceCompletion(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 new file mode 100644 index 000000000..891539b6b --- /dev/null +++ b/cmd/compose/completion.go @@ -0,0 +1,48 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "strings" + + "github.com/spf13/cobra" +) + +// validArgsFn defines a completion func to be returned to fetch completion options +type validArgsFn func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) + +func noCompletion() validArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveNoFileComp + } +} + +func serviceCompletion(p *projectOptions) validArgsFn { + return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + project, err := p.toProject(nil) + if err != nil { + return nil, cobra.ShellCompDirectiveNoFileComp + } + var serviceNames []string + for _, s := range project.ServiceNames() { + if toComplete == "" || strings.HasPrefix(s, toComplete) { + serviceNames = append(serviceNames, s) + } + } + return serviceNames, cobra.ShellCompDirectiveNoFileComp + } +} diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index b2f4213fd..85fa4f7ab 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -41,10 +41,10 @@ import ( "github.com/docker/compose-cli/pkg/compose" ) -//Command defines a compose CLI command as a func with args +// Command defines a compose CLI command as a func with args type Command func(context.Context, []string) error -//Adapt a Command func to cobra library +// Adapt a Command func to cobra library func Adapt(fn Command) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() diff --git a/cmd/compose/convert.go b/cmd/compose/convert.go index 69b008cb2..6f9212632 100644 --- a/cmd/compose/convert.go +++ b/cmd/compose/convert.go @@ -86,6 +86,7 @@ func convertCommand(p *projectOptions, backend api.Service) *cobra.Command { return runConvert(ctx, backend, opts, args) }), + ValidArgsFunction: serviceCompletion(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 7a84209c9..99f0952fd 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -60,6 +60,7 @@ func copyCommand(p *projectOptions, backend api.Service) *cobra.Command { opts.destination = args[1] return runCopy(ctx, backend, opts) }), + ValidArgsFunction: serviceCompletion(p), } flags := copyCmd.Flags() diff --git a/cmd/compose/create.go b/cmd/compose/create.go index 04dd31250..77c3087c4 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -64,6 +64,7 @@ func createCommand(p *projectOptions, backend api.Service) *cobra.Command { QuietPull: false, }) }), + ValidArgsFunction: serviceCompletion(p), } flags := cmd.Flags() flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.") diff --git a/cmd/compose/down.go b/cmd/compose/down.go index a4ca24a8b..e851fb8c3 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -57,6 +57,7 @@ func downCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runDown(ctx, backend, opts) }), + ValidArgsFunction: noCompletion(), } flags := downCmd.Flags() flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.") diff --git a/cmd/compose/events.go b/cmd/compose/events.go index dec921e71..6f7368e95 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -43,6 +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), } 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 ce6220f6e..653a44417 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -61,6 +61,7 @@ func execCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runExec(ctx, backend, opts) }), + ValidArgsFunction: serviceCompletion(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 ea77e9122..4da71f7eb 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -48,6 +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), } 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 977cd88ae..d4b33a115 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -33,6 +33,7 @@ func killCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: p.WithProject(func(ctx context.Context, project *types.Project) error { return backend.Kill(ctx, project, opts) }), + ValidArgsFunction: serviceCompletion(p), } flags := cmd.Flags() diff --git a/cmd/compose/list.go b/cmd/compose/list.go index 56ba30dee..83b51ceb9 100644 --- a/cmd/compose/list.go +++ b/cmd/compose/list.go @@ -46,6 +46,7 @@ func listCommand(contextType string, backend api.Service) *cobra.Command { RunE: Adapt(func(ctx context.Context, args []string) error { return runList(ctx, backend, opts) }), + ValidArgsFunction: noCompletion(), } lsCmd.Flags().StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json].") lsCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs.") diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index 44040fa55..88ee5d5ba 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -44,11 +44,12 @@ func logsCommand(p *projectOptions, contextType string, backend api.Service) *co projectOptions: p, } logsCmd := &cobra.Command{ - Use: "logs [service...]", + Use: "logs [SERVICE...]", Short: "View output from containers", RunE: Adapt(func(ctx context.Context, args []string) error { return runLogs(ctx, backend, opts, args) }), + ValidArgsFunction: serviceCompletion(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 66ec751e6..52c7fbb6a 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -38,6 +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), } return cmd } @@ -67,6 +68,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), } return cmd } diff --git a/cmd/compose/port.go b/cmd/compose/port.go index e1556d96d..37ce0019b 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -52,6 +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), } 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 5249fcf2f..9bb9a6e5a 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -67,8 +67,8 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command { opts := psOptions{ projectOptions: p, } - cmd := &cobra.Command{ - Use: "ps [options] [SERVICE...]", + psCmd := &cobra.Command{ + Use: "ps [SERVICE...]", Short: "List containers", PreRunE: func(cmd *cobra.Command, args []string) error { return opts.parseFilter() @@ -76,8 +76,9 @@ 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), } - flags := cmd.Flags() + flags := psCmd.Flags() flags.StringVar(&opts.Format, "format", "pretty", "Format the output. Values: [pretty | json]") flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property") flags.StringVar(&opts.Status, "status", "", "Filter services by status") @@ -85,7 +86,7 @@ func psCommand(p *projectOptions, backend api.Service) *cobra.Command { flags.BoolVar(&opts.Services, "services", false, "Display services") flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)") flags.Lookup("filter").Hidden = true - return cmd + return psCmd } func runPs(ctx context.Context, backend api.Service, services []string, opts psOptions) error { diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 6d3417ca2..c7213cc6c 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -54,6 +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), } 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 5632bb649..906578dd3 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -41,6 +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), } 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 a52b4f715..924cdab23 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -46,6 +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), } 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 891c2f0b9..8a022ed9d 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -40,6 +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), } 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 0b79927aa..cdaeeab08 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -126,6 +126,7 @@ func runCommand(p *projectOptions, backend api.Service) *cobra.Command { } return runRun(ctx, backend, project, opts) }), + ValidArgsFunction: serviceCompletion(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 57e48f811..d3d46245b 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -37,6 +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), } return startCmd } diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index d8a08c2f4..81b90ad93 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -44,6 +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), } 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 ab81dd64c..441deeaa2 100644 --- a/cmd/compose/top.go +++ b/cmd/compose/top.go @@ -39,11 +39,12 @@ func topCommand(p *projectOptions, backend api.Service) *cobra.Command { projectOptions: p, } topCmd := &cobra.Command{ - Use: "top", + Use: "top [SERVICES...]", Short: "Display the running processes", RunE: Adapt(func(ctx context.Context, args []string) error { return runTop(ctx, backend, opts, args) }), + ValidArgsFunction: serviceCompletion(p), } return topCmd } diff --git a/cmd/compose/up.go b/cmd/compose/up.go index a1e7fcbf9..4cdffa747 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -120,6 +120,7 @@ func upCommand(p *projectOptions, backend api.Service) *cobra.Command { RunE: p.WithServices(func(ctx context.Context, project *types.Project, services []string) error { return runUp(ctx, backend, create, up, project, services) }), + ValidArgsFunction: serviceCompletion(p), } flags := upCmd.Flags() flags.StringArrayVarP(&up.Environment, "environment", "e", []string{}, "Environment variables")