Browse Source

Merge pull request #23886 from AkihiroSuda/stackcli

add `docker stack ls`
Sebastiaan van Stijn 8 years ago
parent
commit
a4dd51a660

+ 1 - 0
cli/command/stack/cmd_experimental.go

@@ -23,6 +23,7 @@ func NewStackCommand(dockerCli *command.DockerCli) *cobra.Command {
 	cmd.AddCommand(
 		newConfigCommand(dockerCli),
 		newDeployCommand(dockerCli),
+		newListCommand(dockerCli),
 		newRemoveCommand(dockerCli),
 		newServicesCommand(dockerCli),
 		newPsCommand(dockerCli),

+ 119 - 0
cli/command/stack/list.go

@@ -0,0 +1,119 @@
+// +build experimental
+
+package stack
+
+import (
+	"fmt"
+	"io"
+	"strconv"
+	"text/tabwriter"
+
+	"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/client"
+	"github.com/spf13/cobra"
+)
+
+const (
+	listItemFmt = "%s\t%s\n"
+)
+
+type listOptions struct {
+}
+
+func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
+	opts := listOptions{}
+
+	cmd := &cobra.Command{
+		Use:     "ls",
+		Aliases: []string{"list"},
+		Short:   "List stacks",
+		Args:    cli.NoArgs,
+		RunE: func(cmd *cobra.Command, args []string) error {
+			return runList(dockerCli, opts)
+		},
+	}
+
+	return cmd
+}
+
+func runList(dockerCli *command.DockerCli, opts listOptions) error {
+	client := dockerCli.Client()
+	ctx := context.Background()
+
+	stacks, err := getStacks(ctx, client)
+	if err != nil {
+		return err
+	}
+
+	out := dockerCli.Out()
+	printTable(out, stacks)
+	return nil
+}
+
+func printTable(out io.Writer, stacks []*stack) {
+	writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
+
+	// Ignore flushing errors
+	defer writer.Flush()
+
+	fmt.Fprintf(writer, listItemFmt, "NAME", "SERVICES")
+	for _, stack := range stacks {
+		fmt.Fprintf(
+			writer,
+			listItemFmt,
+			stack.Name,
+			strconv.Itoa(stack.Services),
+		)
+	}
+}
+
+type stack struct {
+	// Name is the name of the stack
+	Name string
+	// Services is the number of the services
+	Services int
+}
+
+func getStacks(
+	ctx context.Context,
+	apiclient client.APIClient,
+) ([]*stack, error) {
+
+	filter := filters.NewArgs()
+	filter.Add("label", labelNamespace)
+
+	services, err := apiclient.ServiceList(
+		ctx,
+		types.ServiceListOptions{Filter: filter})
+	if err != nil {
+		return nil, err
+	}
+	m := make(map[string]*stack, 0)
+	for _, service := range services {
+		labels := service.Spec.Labels
+		name, ok := labels[labelNamespace]
+		if !ok {
+			return nil, fmt.Errorf("cannot get label %s for service %s",
+				labelNamespace, service.ID)
+		}
+		ztack, ok := m[name]
+		if !ok {
+			m[name] = &stack{
+				Name:     name,
+				Services: 1,
+			}
+		} else {
+			ztack.Services++
+		}
+	}
+	var stacks []*stack
+	for _, stack := range m {
+		stacks = append(stacks, stack)
+	}
+	return stacks, nil
+}

+ 0 - 4
cli/command/stack/services.go

@@ -16,10 +16,6 @@ import (
 	"github.com/spf13/cobra"
 )
 
-const (
-	listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
-)
-
 type servicesOptions struct {
 	quiet     bool
 	filter    opts.FilterOpt

+ 1 - 0
docs/reference/commandline/stack_config.md

@@ -29,3 +29,4 @@ Displays the configuration of a stack.
 * [stack rm](stack_rm.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.md)
+* [stack ls](stack_ls.md)

+ 1 - 0
docs/reference/commandline/stack_deploy.md

@@ -58,3 +58,4 @@ axqh55ipl40h  vossibility-stack_vossibility-collector  1 icecrime/vossibility-co
 * [stack rm](stack_rm.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.md)
+* [stack ls](stack_ls.md)

+ 37 - 0
docs/reference/commandline/stack_ls.md

@@ -0,0 +1,37 @@
+<!--[metadata]>
++++
+title = "stack ls"
+description = "The stack ls command description and usage"
+keywords = ["stack, ls"]
+advisory = "experimental"
+[menu.main]
+parent = "smn_cli"
++++
+<![end-metadata]-->
+
+# stack ls (experimental)
+
+```markdown
+Usage:	docker stack ls
+
+List stacks
+```
+
+Lists the stacks.
+
+For example, the following command shows all stacks and some additional information:
+
+```bash
+$ docker stack ls
+
+ID                 SERVICES
+vossibility-stack  6
+myapp              2
+```
+
+## Related information
+
+* [stack config](stack_config.md)
+* [stack deploy](stack_deploy.md)
+* [stack rm](stack_rm.md)
+* [stack tasks](stack_tasks.md)

+ 1 - 0
docs/reference/commandline/stack_rm.md

@@ -32,3 +32,4 @@ a manager node.
 * [stack deploy](stack_deploy.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.md)
+* [stack ls](stack_ls.md)

+ 1 - 0
docs/reference/commandline/stack_services.md

@@ -63,3 +63,4 @@ The currently supported filters are:
 * [stack deploy](stack_deploy.md)
 * [stack rm](stack_rm.md)
 * [stack tasks](stack_tasks.md)
+* [stack ls](stack_ls.md)

+ 1 - 0
docs/reference/commandline/stack_tasks.md

@@ -45,3 +45,4 @@ The currently supported filters are:
 * [stack deploy](stack_deploy.md)
 * [stack rm](stack_rm.md)
 * [stack services](stack_services.md)
+* [stack ls](stack_ls.md)

+ 1 - 0
experimental/docker-stacks-and-bundles.md

@@ -93,6 +93,7 @@ Options:
 Commands:
   config      Print the stack configuration
   deploy      Create and update a stack
+  ls          List stacks
   rm          Remove the stack
   services    List the services in the stack
   tasks       List the tasks in the stack

+ 54 - 0
integration-cli/docker_cli_stack_test.go

@@ -3,6 +3,9 @@
 package main
 
 import (
+	"io/ioutil"
+	"os"
+
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 )
@@ -36,3 +39,54 @@ func (s *DockerSwarmSuite) TestStackServices(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n")
 }
+
+// testDAB is the DAB JSON used for testing.
+// TODO: Use template/text and substitute "Image" with the result of
+// `docker inspect --format '{{index .RepoDigests 0}}' busybox:latest`
+const testDAB = `{
+    "Version": "0.1",
+    "Services": {
+	"srv1": {
+	    "Image": "busybox@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0",
+	    "Command": ["top"]
+	},
+	"srv2": {
+	    "Image": "busybox@sha256:e4f93f6ed15a0cdd342f5aae387886fba0ab98af0a102da6276eaf24d6e6ade0",
+	    "Command": ["tail"],
+	    "Args": ["-f", "/dev/null"]
+	}
+    }
+}`
+
+func (s *DockerSwarmSuite) TestStackWithDAB(c *check.C) {
+	// setup
+	testStackName := "test"
+	testDABFileName := testStackName + ".dab"
+	defer os.RemoveAll(testDABFileName)
+	err := ioutil.WriteFile(testDABFileName, []byte(testDAB), 0444)
+	c.Assert(err, checker.IsNil)
+	d := s.AddDaemon(c, true, true)
+	// deploy
+	stackArgs := []string{"stack", "deploy", testStackName}
+	out, err := d.Cmd(stackArgs...)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "Loading bundle from test.dab\n")
+	c.Assert(out, checker.Contains, "Creating service test_srv1\n")
+	c.Assert(out, checker.Contains, "Creating service test_srv2\n")
+	// ls
+	stackArgs = []string{"stack", "ls"}
+	out, err = d.Cmd(stackArgs...)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, check.Equals, "NAME  SERVICES\n"+"test  2\n")
+	// rm
+	stackArgs = []string{"stack", "rm", testStackName}
+	out, err = d.Cmd(stackArgs...)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "Removing service test_srv1\n")
+	c.Assert(out, checker.Contains, "Removing service test_srv2\n")
+	// ls (empty)
+	stackArgs = []string{"stack", "ls"}
+	out, err = d.Cmd(stackArgs...)
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, check.Equals, "NAME  SERVICES\n")
+}