github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/service/ps.go (about) 1 package service 2 3 import ( 4 "context" 5 "strings" 6 7 "github.com/docker/cli/cli" 8 "github.com/docker/cli/cli/command" 9 "github.com/docker/cli/cli/command/idresolver" 10 "github.com/docker/cli/cli/command/node" 11 "github.com/docker/cli/cli/command/task" 12 "github.com/docker/cli/opts" 13 "github.com/docker/docker/api/types" 14 "github.com/docker/docker/api/types/filters" 15 "github.com/docker/docker/client" 16 "github.com/pkg/errors" 17 "github.com/spf13/cobra" 18 ) 19 20 type psOptions struct { 21 services []string 22 quiet bool 23 noResolve bool 24 noTrunc bool 25 format string 26 filter opts.FilterOpt 27 } 28 29 func newPsCommand(dockerCli command.Cli) *cobra.Command { 30 options := psOptions{filter: opts.NewFilterOpt()} 31 32 cmd := &cobra.Command{ 33 Use: "ps [OPTIONS] SERVICE [SERVICE...]", 34 Short: "List the tasks of one or more services", 35 Args: cli.RequiresMinArgs(1), 36 RunE: func(cmd *cobra.Command, args []string) error { 37 options.services = args 38 return runPS(cmd.Context(), dockerCli, options) 39 }, 40 ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 41 return CompletionFn(dockerCli)(cmd, args, toComplete) 42 }, 43 } 44 flags := cmd.Flags() 45 flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") 46 flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") 47 flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names") 48 flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template") 49 flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") 50 51 return cmd 52 } 53 54 func runPS(ctx context.Context, dockerCli command.Cli, options psOptions) error { 55 apiClient := dockerCli.Client() 56 57 filter, notfound, err := createFilter(ctx, apiClient, options) 58 if err != nil { 59 return err 60 } 61 if err := updateNodeFilter(ctx, apiClient, filter); err != nil { 62 return err 63 } 64 65 tasks, err := apiClient.TaskList(ctx, types.TaskListOptions{Filters: filter}) 66 if err != nil { 67 return err 68 } 69 70 format := options.format 71 if len(format) == 0 { 72 format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet) 73 } 74 if options.quiet { 75 options.noTrunc = true 76 } 77 if err := task.Print(ctx, dockerCli, tasks, idresolver.New(apiClient, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { 78 return err 79 } 80 if len(notfound) != 0 { 81 return errors.New(strings.Join(notfound, "\n")) 82 } 83 return nil 84 } 85 86 func createFilter(ctx context.Context, apiClient client.APIClient, options psOptions) (filters.Args, []string, error) { 87 filter := options.filter.Value() 88 89 serviceIDFilter := filters.NewArgs() 90 serviceNameFilter := filters.NewArgs() 91 for _, service := range options.services { 92 serviceIDFilter.Add("id", service) 93 serviceNameFilter.Add("name", service) 94 } 95 serviceByIDList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter}) 96 if err != nil { 97 return filter, nil, err 98 } 99 serviceByNameList, err := apiClient.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter}) 100 if err != nil { 101 return filter, nil, err 102 } 103 104 var notfound []string 105 serviceCount := 0 106 loop: 107 // Match services by 1. Full ID, 2. Full name, 3. ID prefix. An error is returned if the ID-prefix match is ambiguous 108 for _, service := range options.services { 109 for _, s := range serviceByIDList { 110 if s.ID == service { 111 filter.Add("service", s.ID) 112 serviceCount++ 113 continue loop 114 } 115 } 116 for _, s := range serviceByNameList { 117 if s.Spec.Annotations.Name == service { 118 filter.Add("service", s.ID) 119 serviceCount++ 120 continue loop 121 } 122 } 123 found := false 124 for _, s := range serviceByIDList { 125 if strings.HasPrefix(s.ID, service) { 126 if found { 127 return filter, nil, errors.New("multiple services found with provided prefix: " + service) 128 } 129 filter.Add("service", s.ID) 130 serviceCount++ 131 found = true 132 } 133 } 134 if !found { 135 notfound = append(notfound, "no such service: "+service) 136 } 137 } 138 if serviceCount == 0 { 139 return filter, nil, errors.New(strings.Join(notfound, "\n")) 140 } 141 return filter, notfound, err 142 } 143 144 func updateNodeFilter(ctx context.Context, apiClient client.APIClient, filter filters.Args) error { 145 if filter.Contains("node") { 146 nodeFilters := filter.Get("node") 147 for _, nodeFilter := range nodeFilters { 148 nodeReference, err := node.Reference(ctx, apiClient, nodeFilter) 149 if err != nil { 150 return err 151 } 152 filter.Del("node", nodeFilter) 153 filter.Add("node", nodeReference) 154 } 155 } 156 return nil 157 }