github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/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(dockerCli, options) 39 }, 40 } 41 flags := cmd.Flags() 42 flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs") 43 flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output") 44 flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names") 45 flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template") 46 flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") 47 48 return cmd 49 } 50 51 func runPS(dockerCli command.Cli, options psOptions) error { 52 client := dockerCli.Client() 53 ctx := context.Background() 54 55 filter, notfound, err := createFilter(ctx, client, options) 56 if err != nil { 57 return err 58 } 59 if err := updateNodeFilter(ctx, client, filter); err != nil { 60 return err 61 } 62 63 tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) 64 if err != nil { 65 return err 66 } 67 68 format := options.format 69 if len(format) == 0 { 70 format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet) 71 } 72 if options.quiet { 73 options.noTrunc = true 74 } 75 if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil { 76 return err 77 } 78 if len(notfound) != 0 { 79 return errors.New(strings.Join(notfound, "\n")) 80 } 81 return nil 82 } 83 84 func createFilter(ctx context.Context, client client.APIClient, options psOptions) (filters.Args, []string, error) { 85 filter := options.filter.Value() 86 87 serviceIDFilter := filters.NewArgs() 88 serviceNameFilter := filters.NewArgs() 89 for _, service := range options.services { 90 serviceIDFilter.Add("id", service) 91 serviceNameFilter.Add("name", service) 92 } 93 serviceByIDList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter}) 94 if err != nil { 95 return filter, nil, err 96 } 97 serviceByNameList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter}) 98 if err != nil { 99 return filter, nil, err 100 } 101 102 var notfound []string 103 serviceCount := 0 104 loop: 105 // Match services by 1. Full ID, 2. Full name, 3. ID prefix. An error is returned if the ID-prefix match is ambiguous 106 for _, service := range options.services { 107 for _, s := range serviceByIDList { 108 if s.ID == service { 109 filter.Add("service", s.ID) 110 serviceCount++ 111 continue loop 112 } 113 } 114 for _, s := range serviceByNameList { 115 if s.Spec.Annotations.Name == service { 116 filter.Add("service", s.ID) 117 serviceCount++ 118 continue loop 119 } 120 } 121 found := false 122 for _, s := range serviceByIDList { 123 if strings.HasPrefix(s.ID, service) { 124 if found { 125 return filter, nil, errors.New("multiple services found with provided prefix: " + service) 126 } 127 filter.Add("service", s.ID) 128 serviceCount++ 129 found = true 130 } 131 } 132 if !found { 133 notfound = append(notfound, "no such service: "+service) 134 } 135 } 136 if serviceCount == 0 { 137 return filter, nil, errors.New(strings.Join(notfound, "\n")) 138 } 139 return filter, notfound, err 140 } 141 142 func updateNodeFilter(ctx context.Context, client client.APIClient, filter filters.Args) error { 143 if filter.Contains("node") { 144 nodeFilters := filter.Get("node") 145 for _, nodeFilter := range nodeFilters { 146 nodeReference, err := node.Reference(ctx, client, nodeFilter) 147 if err != nil { 148 return err 149 } 150 filter.Del("node", nodeFilter) 151 filter.Add("node", nodeReference) 152 } 153 } 154 return nil 155 }