list.go 2.4 KB

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