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(
 	cmd.AddCommand(
 		newConfigCommand(dockerCli),
 		newConfigCommand(dockerCli),
 		newDeployCommand(dockerCli),
 		newDeployCommand(dockerCli),
+		newListCommand(dockerCli),
 		newRemoveCommand(dockerCli),
 		newRemoveCommand(dockerCli),
 		newServicesCommand(dockerCli),
 		newServicesCommand(dockerCli),
 		newPsCommand(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"
 	"github.com/spf13/cobra"
 )
 )
 
 
-const (
-	listItemFmt = "%s\t%s\t%s\t%s\t%s\n"
-)
-
 type servicesOptions struct {
 type servicesOptions struct {
 	quiet     bool
 	quiet     bool
 	filter    opts.FilterOpt
 	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 rm](stack_rm.md)
 * [stack services](stack_services.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.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 rm](stack_rm.md)
 * [stack services](stack_services.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.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 deploy](stack_deploy.md)
 * [stack services](stack_services.md)
 * [stack services](stack_services.md)
 * [stack tasks](stack_tasks.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 deploy](stack_deploy.md)
 * [stack rm](stack_rm.md)
 * [stack rm](stack_rm.md)
 * [stack tasks](stack_tasks.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 deploy](stack_deploy.md)
 * [stack rm](stack_rm.md)
 * [stack rm](stack_rm.md)
 * [stack services](stack_services.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:
 Commands:
   config      Print the stack configuration
   config      Print the stack configuration
   deploy      Create and update a stack
   deploy      Create and update a stack
+  ls          List stacks
   rm          Remove the stack
   rm          Remove the stack
   services    List the services in the stack
   services    List the services in the stack
   tasks       List the tasks 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
 package main
 
 
 import (
 import (
+	"io/ioutil"
+	"os"
+
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 	"github.com/go-check/check"
 )
 )
@@ -36,3 +39,54 @@ func (s *DockerSwarmSuite) TestStackServices(c *check.C) {
 	c.Assert(err, checker.IsNil)
 	c.Assert(err, checker.IsNil)
 	c.Assert(out, check.Equals, "Nothing found in stack: UNKNOWN_STACK\n")
 	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")
+}