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  }