list.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package stack
  2. import (
  3. "fmt"
  4. "io"
  5. "sort"
  6. "strconv"
  7. "text/tabwriter"
  8. "github.com/docker/docker/api/types"
  9. "github.com/docker/docker/cli"
  10. "github.com/docker/docker/cli/command"
  11. "github.com/docker/docker/cli/compose/convert"
  12. "github.com/docker/docker/client"
  13. "github.com/pkg/errors"
  14. "github.com/spf13/cobra"
  15. "golang.org/x/net/context"
  16. )
  17. const (
  18. listItemFmt = "%s\t%s\n"
  19. )
  20. type listOptions struct {
  21. }
  22. func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
  23. opts := listOptions{}
  24. cmd := &cobra.Command{
  25. Use: "ls",
  26. Aliases: []string{"list"},
  27. Short: "List stacks",
  28. Args: cli.NoArgs,
  29. RunE: func(cmd *cobra.Command, args []string) error {
  30. return runList(dockerCli, opts)
  31. },
  32. }
  33. return cmd
  34. }
  35. func runList(dockerCli *command.DockerCli, opts listOptions) error {
  36. client := dockerCli.Client()
  37. ctx := context.Background()
  38. stacks, err := getStacks(ctx, client)
  39. if err != nil {
  40. return err
  41. }
  42. out := dockerCli.Out()
  43. printTable(out, stacks)
  44. return nil
  45. }
  46. type byName []*stack
  47. func (n byName) Len() int { return len(n) }
  48. func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
  49. func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
  50. func printTable(out io.Writer, stacks []*stack) {
  51. writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
  52. // Ignore flushing errors
  53. defer writer.Flush()
  54. sort.Sort(byName(stacks))
  55. fmt.Fprintf(writer, listItemFmt, "NAME", "SERVICES")
  56. for _, stack := range stacks {
  57. fmt.Fprintf(
  58. writer,
  59. listItemFmt,
  60. stack.Name,
  61. strconv.Itoa(stack.Services),
  62. )
  63. }
  64. }
  65. type stack struct {
  66. // Name is the name of the stack
  67. Name string
  68. // Services is the number of the services
  69. Services int
  70. }
  71. func getStacks(
  72. ctx context.Context,
  73. apiclient client.APIClient,
  74. ) ([]*stack, error) {
  75. services, err := apiclient.ServiceList(
  76. ctx,
  77. types.ServiceListOptions{Filters: getAllStacksFilter()})
  78. if err != nil {
  79. return nil, err
  80. }
  81. m := make(map[string]*stack, 0)
  82. for _, service := range services {
  83. labels := service.Spec.Labels
  84. name, ok := labels[convert.LabelNamespace]
  85. if !ok {
  86. return nil, errors.Errorf("cannot get label %s for service %s",
  87. convert.LabelNamespace, service.ID)
  88. }
  89. ztack, ok := m[name]
  90. if !ok {
  91. m[name] = &stack{
  92. Name: name,
  93. Services: 1,
  94. }
  95. } else {
  96. ztack.Services++
  97. }
  98. }
  99. var stacks []*stack
  100. for _, stack := range m {
  101. stacks = append(stacks, stack)
  102. }
  103. return stacks, nil
  104. }