This uses the DefaultShellCompDirective feature which was added in cobra to override the default (which would complete to use files for commands and flags). Note that we set "cobra.NoFileCompletions" for many commands, which is redundant with this change, so we could remove as well. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
156 lines
4.3 KiB
Go
156 lines
4.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli"
|
|
"github.com/docker/cli/cli/command"
|
|
"github.com/docker/cli/cli/command/idresolver"
|
|
"github.com/docker/cli/cli/command/node"
|
|
"github.com/docker/cli/cli/command/task"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/moby/moby/api/types/filters"
|
|
"github.com/moby/moby/client"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type psOptions struct {
|
|
services []string
|
|
quiet bool
|
|
noResolve bool
|
|
noTrunc bool
|
|
format string
|
|
filter opts.FilterOpt
|
|
}
|
|
|
|
func newPsCommand(dockerCLI command.Cli) *cobra.Command {
|
|
options := psOptions{filter: opts.NewFilterOpt()}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "ps [OPTIONS] SERVICE [SERVICE...]",
|
|
Short: "List the tasks of one or more services",
|
|
Args: cli.RequiresMinArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
options.services = args
|
|
return runPS(cmd.Context(), dockerCLI, options)
|
|
},
|
|
ValidArgsFunction: completeServiceNames(dockerCLI),
|
|
DisableFlagsInUseLine: true,
|
|
}
|
|
flags := cmd.Flags()
|
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
|
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
|
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runPS(ctx context.Context, dockerCli command.Cli, options psOptions) error {
|
|
apiClient := dockerCli.Client()
|
|
|
|
filter, notfound, err := createFilter(ctx, apiClient, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := updateNodeFilter(ctx, apiClient, filter); err != nil {
|
|
return err
|
|
}
|
|
|
|
tasks, err := apiClient.TaskList(ctx, client.TaskListOptions{Filters: filter})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
format := options.format
|
|
if len(format) == 0 {
|
|
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
|
|
}
|
|
if options.quiet {
|
|
options.noTrunc = true
|
|
}
|
|
if err := task.Print(ctx, dockerCli, tasks, idresolver.New(apiClient, options.noResolve), !options.noTrunc, options.quiet, format); err != nil {
|
|
return err
|
|
}
|
|
if len(notfound) != 0 {
|
|
return errors.New(strings.Join(notfound, "\n"))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createFilter(ctx context.Context, apiClient client.APIClient, options psOptions) (filters.Args, []string, error) {
|
|
filter := options.filter.Value()
|
|
|
|
serviceIDFilter := filters.NewArgs()
|
|
serviceNameFilter := filters.NewArgs()
|
|
for _, service := range options.services {
|
|
serviceIDFilter.Add("id", service)
|
|
serviceNameFilter.Add("name", service)
|
|
}
|
|
serviceByIDList, err := apiClient.ServiceList(ctx, client.ServiceListOptions{Filters: serviceIDFilter})
|
|
if err != nil {
|
|
return filter, nil, err
|
|
}
|
|
serviceByNameList, err := apiClient.ServiceList(ctx, client.ServiceListOptions{Filters: serviceNameFilter})
|
|
if err != nil {
|
|
return filter, nil, err
|
|
}
|
|
|
|
var notfound []string
|
|
serviceCount := 0
|
|
loop:
|
|
// Match services by 1. Full ID, 2. Full name, 3. ID prefix. An error is returned if the ID-prefix match is ambiguous
|
|
for _, service := range options.services {
|
|
for _, s := range serviceByIDList {
|
|
if s.ID == service {
|
|
filter.Add("service", s.ID)
|
|
serviceCount++
|
|
continue loop
|
|
}
|
|
}
|
|
for _, s := range serviceByNameList {
|
|
if s.Spec.Annotations.Name == service {
|
|
filter.Add("service", s.ID)
|
|
serviceCount++
|
|
continue loop
|
|
}
|
|
}
|
|
found := false
|
|
for _, s := range serviceByIDList {
|
|
if strings.HasPrefix(s.ID, service) {
|
|
if found {
|
|
return filter, nil, errors.New("multiple services found with provided prefix: " + service)
|
|
}
|
|
filter.Add("service", s.ID)
|
|
serviceCount++
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
notfound = append(notfound, "no such service: "+service)
|
|
}
|
|
}
|
|
if serviceCount == 0 {
|
|
return filter, nil, errors.New(strings.Join(notfound, "\n"))
|
|
}
|
|
return filter, notfound, err
|
|
}
|
|
|
|
func updateNodeFilter(ctx context.Context, apiClient client.APIClient, filter filters.Args) error {
|
|
if filter.Contains("node") {
|
|
nodeFilters := filter.Get("node")
|
|
for _, nodeFilter := range nodeFilters {
|
|
nodeReference, err := node.Reference(ctx, apiClient, nodeFilter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
filter.Del("node", nodeFilter)
|
|
filter.Add("node", nodeReference)
|
|
}
|
|
}
|
|
return nil
|
|
}
|