ソースを参照

Local compose ls implementation

Signed-off-by: Guillaume Tardif <[email protected]>
Guillaume Tardif 5 年 前
コミット
9f594abd85
4 ファイル変更140 行追加12 行削除
  1. 65 10
      local/compose.go
  2. 68 0
      local/compose_test.go
  3. 3 2
      local/convergence.go
  4. 4 0
      local/labels.go

+ 65 - 10
local/compose.go

@@ -24,17 +24,12 @@ import (
 	"fmt"
 	"io"
 	"path/filepath"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
 
-	"golang.org/x/sync/errgroup"
-
 	"github.com/compose-spec/compose-go/types"
-	"github.com/docker/compose-cli/api/compose"
-	"github.com/docker/compose-cli/api/containers"
-	"github.com/docker/compose-cli/formatter"
-	"github.com/docker/compose-cli/progress"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/api/types/filters"
@@ -46,6 +41,12 @@ import (
 	"github.com/docker/go-connections/nat"
 	"github.com/pkg/errors"
 	"github.com/sanathkr/go-yaml"
+	"golang.org/x/sync/errgroup"
+
+	"github.com/docker/compose-cli/api/compose"
+	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/formatter"
+	"github.com/docker/compose-cli/progress"
 )
 
 func (s *local) Up(ctx context.Context, project *types.Project, detach bool) error {
@@ -262,13 +263,67 @@ func (s *local) Ps(ctx context.Context, projectName string) ([]compose.ServiceSt
 }
 
 func (s *local) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
-	_, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{All: true})
+	list, err := s.containerService.apiClient.ContainerList(ctx, moby.ContainerListOptions{
+		Filters: filters.NewArgs(hasProjectLabelFilter()),
+	})
 	if err != nil {
 		return nil, err
 	}
-	var stacks []compose.Stack
-	// TODO rebuild stacks based on containers
-	return stacks, nil
+
+	return containersToStacks(list)
+}
+
+func containersToStacks(containers []moby.Container) ([]compose.Stack, error) {
+	statusesByProject := map[string][]string{}
+	keys := []string{}
+	for _, c := range containers {
+		project, ok := c.Labels[projectLabel]
+		if !ok {
+			return nil, fmt.Errorf("No label %q set on container %q of compose project", serviceLabel, c.ID)
+		}
+		projectStatuses, ok := statusesByProject[project]
+		if !ok {
+			projectStatuses = []string{}
+			keys = append(keys, project)
+		}
+		projectStatuses = append(projectStatuses, c.State)
+		statusesByProject[project] = projectStatuses
+	}
+
+	sort.Strings(keys)
+	var projects []compose.Stack
+	for _, project := range keys {
+		statuses := statusesByProject[project]
+		projects = append(projects, compose.Stack{
+			ID:     project,
+			Name:   project,
+			Status: combinedStatus(statuses),
+		})
+	}
+	return projects, nil
+}
+
+func combinedStatus(statuses []string) string {
+	nbByStatus := map[string]int{}
+	keys := []string{}
+	for _, status := range statuses {
+		nb, ok := nbByStatus[status]
+		if !ok {
+			nb = 0
+			keys = append(keys, status)
+		}
+		nbByStatus[status] = nb + 1
+	}
+	sort.Strings(keys)
+	result := ""
+	for _, status := range keys {
+		nb := nbByStatus[status]
+		if result != "" {
+			result = result + ", "
+		}
+		result = result + fmt.Sprintf("%s(%d)", status, nb)
+	}
+	return result
 }
 
 func (s *local) Convert(ctx context.Context, project *types.Project, format string) ([]byte, error) {

+ 68 - 0
local/compose_test.go

@@ -0,0 +1,68 @@
+// +build local
+
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package local
+
+import (
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"gotest.tools/v3/assert"
+
+	"github.com/docker/compose-cli/api/compose"
+)
+
+func TestContainersToStacks(t *testing.T) {
+	containers := []types.Container{
+		{
+			ID:     "service1",
+			State:  "running",
+			Labels: map[string]string{projectLabel: "project1"},
+		},
+		{
+			ID:     "service2",
+			State:  "running",
+			Labels: map[string]string{projectLabel: "project1"},
+		},
+		{
+			ID:     "service3",
+			State:  "running",
+			Labels: map[string]string{projectLabel: "project2"},
+		},
+	}
+	stacks, err := containersToStacks(containers)
+	assert.NilError(t, err)
+	assert.DeepEqual(t, stacks, []compose.Stack{
+		{
+			ID:     "project1",
+			Name:   "project1",
+			Status: "running(2)",
+		},
+		{
+			ID:     "project2",
+			Name:   "project2",
+			Status: "running(1)",
+		},
+	})
+}
+
+func TestStacksMixedStatus(t *testing.T) {
+	assert.Equal(t, combinedStatus([]string{"running"}), "running(1)")
+	assert.Equal(t, combinedStatus([]string{"running", "running", "running"}), "running(3)")
+	assert.Equal(t, combinedStatus([]string{"running", "exited", "running"}), "exited(1), running(2)")
+}

+ 3 - 2
local/convergence.go

@@ -24,12 +24,13 @@ import (
 	"strconv"
 
 	"github.com/compose-spec/compose-go/types"
-	"github.com/docker/compose-cli/api/containers"
-	"github.com/docker/compose-cli/progress"
 	moby "github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/network"
 	"golang.org/x/sync/errgroup"
+
+	"github.com/docker/compose-cli/api/containers"
+	"github.com/docker/compose-cli/progress"
 )
 
 func (s *local) ensureService(ctx context.Context, project *types.Project, service types.ServiceConfig) error {

+ 4 - 0
local/labels.go

@@ -34,3 +34,7 @@ const (
 func projectFilter(projectName string) filters.KeyValuePair {
 	return filters.Arg("label", fmt.Sprintf("%s=%s", projectLabel, projectName))
 }
+
+func hasProjectLabelFilter() filters.KeyValuePair {
+	return filters.Arg("label", projectLabel)
+}