Local compose ls implementation

Signed-off-by: Guillaume Tardif <guillaume.tardif@docker.com>
This commit is contained in:
Guillaume Tardif 2020-11-18 18:54:55 +01:00
parent fbf5b0a7f9
commit 9f594abd85
4 changed files with 140 additions and 12 deletions

View File

@ -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
local/compose_test.go Normal file
View File

@ -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)")
}

View File

@ -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 {

View File

@ -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)
}