diff --git a/cli/command/service/ps.go b/cli/command/service/ps.go index cf94ad7374..12b25bf4f6 100644 --- a/cli/command/service/ps.go +++ b/cli/command/service/ps.go @@ -1,7 +1,13 @@ package service import ( + "fmt" + "strings" + + "golang.org/x/net/context" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" "github.com/docker/docker/cli" "github.com/docker/docker/cli/command" "github.com/docker/docker/cli/command/idresolver" @@ -9,11 +15,10 @@ import ( "github.com/docker/docker/cli/command/task" "github.com/docker/docker/opts" "github.com/spf13/cobra" - "golang.org/x/net/context" ) type psOptions struct { - serviceID string + services []string quiet bool noResolve bool noTrunc bool @@ -24,11 +29,11 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command { opts := psOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ - Use: "ps [OPTIONS] SERVICE", - Short: "List the tasks of a service", - Args: cli.ExactArgs(1), + 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 { - opts.serviceID = args[0] + opts.services = args return runPS(dockerCli, opts) }, } @@ -45,13 +50,46 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error { client := dockerCli.Client() ctx := context.Background() - service, _, err := client.ServiceInspectWithRaw(ctx, opts.serviceID) + filter := opts.filter.Value() + + serviceIDFilter := filters.NewArgs() + serviceNameFilter := filters.NewArgs() + for _, service := range opts.services { + serviceIDFilter.Add("id", service) + serviceNameFilter.Add("name", service) + } + serviceByIDList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceIDFilter}) + if err != nil { + return err + } + serviceByNameList, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceNameFilter}) if err != nil { return err } - filter := opts.filter.Value() - filter.Add("service", service.ID) + for _, service := range opts.services { + serviceCount := 0 + // Lookup by ID/Prefix + for _, serviceEntry := range serviceByIDList { + if strings.HasPrefix(serviceEntry.ID, service) { + filter.Add("service", serviceEntry.ID) + serviceCount++ + } + } + + // Lookup by Name/Prefix + for _, serviceEntry := range serviceByNameList { + if strings.HasPrefix(serviceEntry.Spec.Annotations.Name, service) { + filter.Add("service", serviceEntry.ID) + serviceCount++ + } + } + // If nothing has been found, return immediately. + if serviceCount == 0 { + return fmt.Errorf("no such services: %s", service) + } + } + if filter.Include("node") { nodeFilters := filter.Get("node") for _, nodeFilter := range nodeFilters { diff --git a/docs/reference/commandline/service_ps.md b/docs/reference/commandline/service_ps.md index 61abb15f67..15ac59acaa 100644 --- a/docs/reference/commandline/service_ps.md +++ b/docs/reference/commandline/service_ps.md @@ -19,7 +19,7 @@ aliases: ["/engine/reference/commandline/service_tasks/"] ```Markdown Usage: docker service ps [OPTIONS] SERVICE -List the tasks of a service +List the tasks of one or more services Options: -f, --filter filter Filter output based on conditions provided @@ -29,7 +29,7 @@ Options: -q, --quiet Only display task IDs ``` -Lists the tasks that are running as part of the specified service. This command +Lists the tasks that are running as part of the specified services. This command has to be run targeting a manager node. ## Examples diff --git a/integration-cli/docker_cli_swarm_test.go b/integration-cli/docker_cli_swarm_test.go index a67af79998..4763c4572e 100644 --- a/integration-cli/docker_cli_swarm_test.go +++ b/integration-cli/docker_cli_swarm_test.go @@ -1555,3 +1555,73 @@ func (s *DockerSwarmSuite) TestSwarmNetworkCreateDup(c *check.C) { } } } + +func (s *DockerSwarmSuite) TestSwarmServicePsMultipleServiceIDs(c *check.C) { + d := s.AddDaemon(c, true, true) + + name1 := "top1" + out, err := d.Cmd("service", "create", "--name", name1, "--replicas=3", "busybox", "top") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + id1 := strings.TrimSpace(out) + + name2 := "top2" + out, err = d.Cmd("service", "create", "--name", name2, "--replicas=3", "busybox", "top") + c.Assert(err, checker.IsNil) + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + id2 := strings.TrimSpace(out) + + // make sure task has been deployed. + waitAndAssert(c, defaultReconciliationTimeout, d.CheckActiveContainerCount, checker.Equals, 6) + + out, err = d.Cmd("service", "ps", name1) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name1+".1") + c.Assert(out, checker.Contains, name1+".2") + c.Assert(out, checker.Contains, name1+".3") + c.Assert(out, checker.Not(checker.Contains), name2+".1") + c.Assert(out, checker.Not(checker.Contains), name2+".2") + c.Assert(out, checker.Not(checker.Contains), name2+".3") + + out, err = d.Cmd("service", "ps", name1, name2) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name1+".1") + c.Assert(out, checker.Contains, name1+".2") + c.Assert(out, checker.Contains, name1+".3") + c.Assert(out, checker.Contains, name2+".1") + c.Assert(out, checker.Contains, name2+".2") + c.Assert(out, checker.Contains, name2+".3") + + // Name Prefix + out, err = d.Cmd("service", "ps", "to") + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name1+".1") + c.Assert(out, checker.Contains, name1+".2") + c.Assert(out, checker.Contains, name1+".3") + c.Assert(out, checker.Contains, name2+".1") + c.Assert(out, checker.Contains, name2+".2") + c.Assert(out, checker.Contains, name2+".3") + + // Name Prefix (no hit) + out, err = d.Cmd("service", "ps", "noname") + c.Assert(err, checker.NotNil) + c.Assert(out, checker.Contains, "no such services: noname") + + out, err = d.Cmd("service", "ps", id1) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name1+".1") + c.Assert(out, checker.Contains, name1+".2") + c.Assert(out, checker.Contains, name1+".3") + c.Assert(out, checker.Not(checker.Contains), name2+".1") + c.Assert(out, checker.Not(checker.Contains), name2+".2") + c.Assert(out, checker.Not(checker.Contains), name2+".3") + + out, err = d.Cmd("service", "ps", id1, id2) + c.Assert(err, checker.IsNil) + c.Assert(out, checker.Contains, name1+".1") + c.Assert(out, checker.Contains, name1+".2") + c.Assert(out, checker.Contains, name1+".3") + c.Assert(out, checker.Contains, name2+".1") + c.Assert(out, checker.Contains, name2+".2") + c.Assert(out, checker.Contains, name2+".3") +}