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  }