diff --git a/api/client/service/list.go b/api/client/service/list.go index ab4d4ee805..e0e45decd2 100644 --- a/api/client/service/list.go +++ b/api/client/service/list.go @@ -57,7 +57,7 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error { out := dockerCli.Out() if opts.quiet { - printQuiet(out, services) + PrintQuiet(out, services) } else { taskFilter := filters.NewArgs() for _, service := range services { @@ -73,25 +73,32 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error { if err != nil { return err } - activeNodes := make(map[string]struct{}) - for _, n := range nodes { - if n.Status.State == swarm.NodeStateReady { - activeNodes[n.ID] = struct{}{} - } - } - running := map[string]int{} - for _, task := range tasks { - if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == "running" { - running[task.ServiceID]++ - } - } - - printTable(out, services, running) + PrintNotQuiet(out, services, nodes, tasks) } return nil } +// PrintNotQuiet shows service list in a non-quiet way. +// Besides this, command `docker stack services xxx` will call this, too. +func PrintNotQuiet(out io.Writer, services []swarm.Service, nodes []swarm.Node, tasks []swarm.Task) { + activeNodes := make(map[string]struct{}) + for _, n := range nodes { + if n.Status.State == swarm.NodeStateReady { + activeNodes[n.ID] = struct{}{} + } + } + + running := map[string]int{} + for _, task := range tasks { + if _, nodeActive := activeNodes[task.NodeID]; nodeActive && task.Status.State == "running" { + running[task.ServiceID]++ + } + } + + printTable(out, services, running) +} + func printTable(out io.Writer, services []swarm.Service, running map[string]int) { writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0) @@ -117,7 +124,9 @@ func printTable(out io.Writer, services []swarm.Service, running map[string]int) } } -func printQuiet(out io.Writer, services []swarm.Service) { +// PrintQuiet shows service list in a quiet way. +// Besides this, command `docker stack services xxx` will call this, too. +func PrintQuiet(out io.Writer, services []swarm.Service) { for _, service := range services { fmt.Fprintln(out, service.ID) } diff --git a/api/client/stack/cmd.go b/api/client/stack/cmd.go index 82a7a1ffa6..b0b466469c 100644 --- a/api/client/stack/cmd.go +++ b/api/client/stack/cmd.go @@ -24,6 +24,7 @@ func NewStackCommand(dockerCli *client.DockerCli) *cobra.Command { newConfigCommand(dockerCli), newDeployCommand(dockerCli), newRemoveCommand(dockerCli), + newServicesCommand(dockerCli), newTasksCommand(dockerCli), ) return cmd diff --git a/api/client/stack/services.go b/api/client/stack/services.go new file mode 100644 index 0000000000..3da75460c3 --- /dev/null +++ b/api/client/stack/services.go @@ -0,0 +1,87 @@ +// +build experimental + +package stack + +import ( + "fmt" + + "golang.org/x/net/context" + + "github.com/docker/docker/api/client" + "github.com/docker/docker/api/client/service" + "github.com/docker/docker/cli" + "github.com/docker/docker/opts" + "github.com/docker/engine-api/types" + "github.com/docker/engine-api/types/filters" + "github.com/spf13/cobra" +) + +const ( + listItemFmt = "%s\t%s\t%s\t%s\t%s\n" +) + +type servicesOptions struct { + quiet bool + filter opts.FilterOpt + namespace string +} + +func newServicesCommand(dockerCli *client.DockerCli) *cobra.Command { + opts := servicesOptions{filter: opts.NewFilterOpt()} + + cmd := &cobra.Command{ + Use: "services [OPTIONS] STACK", + Short: "List the services in the stack", + Args: cli.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + opts.namespace = args[0] + return runServices(dockerCli, opts) + }, + } + flags := cmd.Flags() + flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs") + flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided") + + return cmd +} + +func runServices(dockerCli *client.DockerCli, opts servicesOptions) error { + ctx := context.Background() + client := dockerCli.Client() + + filter := opts.filter.Value() + filter.Add("label", labelNamespace+"="+opts.namespace) + + services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: filter}) + if err != nil { + return err + } + + out := dockerCli.Out() + + // if no services in this stack, print message and exit 0 + if len(services) == 0 { + fmt.Fprintf(out, "Nothing found in stack: %s\n", opts.namespace) + return nil + } + + if opts.quiet { + service.PrintQuiet(out, services) + } else { + taskFilter := filters.NewArgs() + for _, service := range services { + taskFilter.Add("service", service.ID) + } + + tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter}) + if err != nil { + return err + } + nodes, err := client.NodeList(ctx, types.NodeListOptions{}) + if err != nil { + return err + } + service.PrintNotQuiet(out, services, nodes, tasks) + } + return nil +} diff --git a/experimental/docker-stacks-and-bundles.md b/experimental/docker-stacks-and-bundles.md index 1da2b8f587..ac70fc3c2e 100644 --- a/experimental/docker-stacks-and-bundles.md +++ b/experimental/docker-stacks-and-bundles.md @@ -93,6 +93,7 @@ Commands: config Print the stack configuration deploy Create and update a stack rm Remove the stack + services List the services in the stack tasks List the tasks in the stack Run 'docker stack COMMAND --help' for more information on a command. diff --git a/integration-cli/docker_cli_stack_test.go b/integration-cli/docker_cli_stack_test.go index cbfa588a14..08b1a209fd 100644 --- a/integration-cli/docker_cli_stack_test.go +++ b/integration-cli/docker_cli_stack_test.go @@ -26,3 +26,13 @@ func (s *DockerSwarmSuite) TestStackTasks(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n") } + +func (s *DockerSwarmSuite) TestStackServices(c *check.C) { + d := s.AddDaemon(c, true, true) + + stackArgs := append([]string{"services", "UNKNOWN_STACK"}) + + out, err := d.Cmd("stack", stackArgs...) + c.Assert(err, checker.IsNil) + c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n") +}