Files
docker-cli/cli/command/service/ps.go
Sebastiaan van Stijn 4f7c07cfc2 update local code for updated modules
Some tests had to be skipped as there's some issues to address, and
some of the result-types cannot be mocked / stubbed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-24 10:28:54 +02:00

155 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/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) (client.Filters, []string, error) {
filter := options.filter.Value()
serviceIDFilter := make(client.Filters)
serviceNameFilter := make(client.Filters)
for _, service := range options.services {
serviceIDFilter.Add("id", service)
serviceNameFilter.Add("name", service)
}
serviceByID, err := apiClient.ServiceList(ctx, client.ServiceListOptions{Filters: serviceIDFilter})
if err != nil {
return filter, nil, err
}
serviceByName, 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 serviceByID.Items {
if s.ID == service {
filter.Add("service", s.ID)
serviceCount++
continue loop
}
}
for _, s := range serviceByName.Items {
if s.Spec.Annotations.Name == service {
filter.Add("service", s.ID)
serviceCount++
continue loop
}
}
found := false
for _, s := range serviceByID.Items {
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 client.Filters) error {
if nodeFilters, ok := filter["node"]; ok {
for nodeFilter := range nodeFilters {
nodeReference, err := node.Reference(ctx, apiClient, nodeFilter)
if err != nil {
return err
}
// TODO(thaJeztah): add utility to remove?
delete(filter["node"], nodeFilter)
filter.Add("node", nodeReference)
}
}
return nil
}