From 9f594abd85e600ee6bca7a731e5f9d0a99512310 Mon Sep 17 00:00:00 2001 From: Guillaume Tardif Date: Wed, 18 Nov 2020 18:54:55 +0100 Subject: [PATCH] Local compose ls implementation Signed-off-by: Guillaume Tardif --- local/compose.go | 75 +++++++++++++++++++++++++++++++++++++------ local/compose_test.go | 68 +++++++++++++++++++++++++++++++++++++++ local/convergence.go | 5 +-- local/labels.go | 4 +++ 4 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 local/compose_test.go diff --git a/local/compose.go b/local/compose.go index fc821877..de6fe593 100644 --- a/local/compose.go +++ b/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) { diff --git a/local/compose_test.go b/local/compose_test.go new file mode 100644 index 00000000..1fca806d --- /dev/null +++ b/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)") +} diff --git a/local/convergence.go b/local/convergence.go index b4c46818..86984a40 100644 --- a/local/convergence.go +++ b/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 { diff --git a/local/labels.go b/local/labels.go index 300e890e..24dee33f 100644 --- a/local/labels.go +++ b/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) +}