mirror of
https://github.com/docker/compose.git
synced 2025-07-24 22:24:41 +02:00
Merge pull request #1019 from docker/ps_containers
This commit is contained in:
commit
a95a76291b
@ -29,6 +29,7 @@ import (
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/context/store"
|
||||
"github.com/docker/compose-cli/errdefs"
|
||||
"github.com/docker/compose-cli/utils/formatter"
|
||||
)
|
||||
|
||||
type aciComposeService struct {
|
||||
@ -119,7 +120,7 @@ func (cs *aciComposeService) Down(ctx context.Context, project string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
|
||||
func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.ContainerSummary, error) {
|
||||
groupsClient, err := login.NewContainerGroupsClient(cs.ctx.SubscriptionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -134,12 +135,30 @@ func (cs *aciComposeService) Ps(ctx context.Context, project string) ([]compose.
|
||||
return nil, fmt.Errorf("no containers found in ACI container group %s", project)
|
||||
}
|
||||
|
||||
res := []compose.ServiceStatus{}
|
||||
res := []compose.ContainerSummary{}
|
||||
for _, container := range *group.Containers {
|
||||
if isContainerVisible(container, group, false) {
|
||||
continue
|
||||
}
|
||||
res = append(res, convert.ContainerGroupToServiceStatus(getContainerID(group, container), group, container, cs.ctx.Location))
|
||||
var publishers []compose.PortPublisher
|
||||
urls := formatter.PortsToStrings(convert.ToPorts(group.IPAddress, *container.Ports), convert.FQDN(group, cs.ctx.Location))
|
||||
for i, p := range *container.Ports {
|
||||
publishers = append(publishers, compose.PortPublisher{
|
||||
URL: urls[i],
|
||||
TargetPort: int(*p.Port),
|
||||
PublishedPort: int(*p.Port),
|
||||
Protocol: string(p.Protocol),
|
||||
})
|
||||
}
|
||||
id := getContainerID(group, container)
|
||||
res = append(res, compose.ContainerSummary{
|
||||
ID: id,
|
||||
Name: id,
|
||||
Project: project,
|
||||
Service: *container.Name,
|
||||
State: convert.GetStatus(container, group),
|
||||
Publishers: publishers,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
@ -314,13 +314,14 @@ func ContainerGroupToServiceStatus(containerID string, group containerinstance.C
|
||||
return compose.ServiceStatus{
|
||||
ID: containerID,
|
||||
Name: *container.Name,
|
||||
Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports), fqdn(group, region)),
|
||||
Ports: formatter.PortsToStrings(ToPorts(group.IPAddress, *container.Ports), FQDN(group, region)),
|
||||
Replicas: replicas,
|
||||
Desired: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func fqdn(group containerinstance.ContainerGroup, region string) string {
|
||||
// FQDN retrieve the fully qualified domain name for a ContainerGroup
|
||||
func FQDN(group containerinstance.ContainerGroup, region string) string {
|
||||
fqdn := ""
|
||||
if group.IPAddress != nil && group.IPAddress.DNSNameLabel != nil && *group.IPAddress.DNSNameLabel != "" {
|
||||
fqdn = *group.IPAddress.DNSNameLabel + "." + region + ".azurecontainer.io"
|
||||
@ -348,7 +349,7 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
|
||||
|
||||
hostConfig := ToHostConfig(cc, cg)
|
||||
config := &containers.RuntimeConfig{
|
||||
FQDN: fqdn(cg, region),
|
||||
FQDN: FQDN(cg, region),
|
||||
Env: envVars,
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@ func (c *composeService) Logs(context.Context, string, compose.LogConsumer) erro
|
||||
return errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
func (c *composeService) Ps(context.Context, string) ([]compose.ServiceStatus, error) {
|
||||
func (c *composeService) Ps(context.Context, string) ([]compose.ContainerSummary, error) {
|
||||
return nil, errdefs.ErrNotImplemented
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ type Service interface {
|
||||
// Logs executes the equivalent to a `compose logs`
|
||||
Logs(ctx context.Context, projectName string, consumer LogConsumer) error
|
||||
// Ps executes the equivalent to a `compose ps`
|
||||
Ps(ctx context.Context, projectName string) ([]ServiceStatus, error)
|
||||
Ps(ctx context.Context, projectName string) ([]ContainerSummary, error)
|
||||
// List executes the equivalent to a `docker stack ls`
|
||||
List(ctx context.Context, projectName string) ([]Stack, error)
|
||||
// Convert translate compose model into backend's native format
|
||||
@ -56,6 +56,16 @@ type PortPublisher struct {
|
||||
Protocol string
|
||||
}
|
||||
|
||||
// ContainerSummary hold high-level description of a container
|
||||
type ContainerSummary struct {
|
||||
ID string
|
||||
Name string
|
||||
Project string
|
||||
Service string
|
||||
State string
|
||||
Publishers []PortPublisher
|
||||
}
|
||||
|
||||
// ServiceStatus hold status about a service
|
||||
type ServiceStatus struct {
|
||||
ID string
|
||||
|
@ -21,12 +21,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose-cli/api/client"
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
"github.com/docker/compose-cli/formatter"
|
||||
)
|
||||
|
||||
@ -54,44 +54,34 @@ func runPs(ctx context.Context, opts composeOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serviceList, err := c.ComposeService().Ps(ctx, projectName)
|
||||
containers, err := c.ComposeService().Ps(ctx, projectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if opts.Quiet {
|
||||
for _, s := range serviceList {
|
||||
for _, s := range containers {
|
||||
fmt.Println(s.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
view := viewFromServiceStatusList(serviceList)
|
||||
return formatter.Print(view, opts.Format, os.Stdout,
|
||||
|
||||
sort.Slice(containers, func(i, j int) bool {
|
||||
return containers[i].Name < containers[j].Name
|
||||
})
|
||||
|
||||
return formatter.Print(containers, opts.Format, os.Stdout,
|
||||
func(w io.Writer) {
|
||||
for _, service := range view {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", "))
|
||||
for _, container := range containers {
|
||||
var ports []string
|
||||
for _, p := range container.Publishers {
|
||||
if p.URL == "" {
|
||||
ports = append(ports, fmt.Sprintf("%d/%s", p.TargetPort, p.Protocol))
|
||||
} else {
|
||||
ports = append(ports, fmt.Sprintf("%s->%d/%s", p.URL, p.TargetPort, p.Protocol))
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", container.Name, container.Service, container.State, strings.Join(ports, ", "))
|
||||
}
|
||||
},
|
||||
"ID", "NAME", "REPLICAS", "PORTS")
|
||||
}
|
||||
|
||||
type serviceStatusView struct {
|
||||
ID string
|
||||
Name string
|
||||
Replicas int
|
||||
Desired int
|
||||
Ports []string
|
||||
}
|
||||
|
||||
func viewFromServiceStatusList(serviceStatusList []compose.ServiceStatus) []serviceStatusView {
|
||||
retList := make([]serviceStatusView, len(serviceStatusList))
|
||||
for i, s := range serviceStatusList {
|
||||
retList[i] = serviceStatusView{
|
||||
ID: s.ID,
|
||||
Name: s.Name,
|
||||
Replicas: s.Replicas,
|
||||
Desired: s.Desired,
|
||||
Ports: s.Ports,
|
||||
}
|
||||
}
|
||||
return retList
|
||||
"NAME", "SERVICE", "STATE", "PORTS")
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ type API interface {
|
||||
DeleteSecret(ctx context.Context, id string, recover bool) error
|
||||
GetLogs(ctx context.Context, name string, consumer func(service, container, message string)) error
|
||||
DescribeService(ctx context.Context, cluster string, arn string) (compose.ServiceStatus, error)
|
||||
DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]compose.ContainerSummary, error)
|
||||
getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error)
|
||||
ListTasks(ctx context.Context, cluster string, family string) ([]string, error)
|
||||
GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error)
|
||||
|
@ -224,6 +224,21 @@ func (mr *MockAPIMockRecorder) DescribeService(arg0, arg1, arg2 interface{}) *go
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*MockAPI)(nil).DescribeService), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// DescribeServiceTasks mocks base method
|
||||
func (m *MockAPI) DescribeServiceTasks(arg0 context.Context, arg1, arg2, arg3 string) ([]compose.ContainerSummary, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DescribeServiceTasks", arg0, arg1, arg2, arg3)
|
||||
ret0, _ := ret[0].([]compose.ContainerSummary)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DescribeServiceTasks indicates an expected call of DescribeServiceTasks
|
||||
func (mr *MockAPIMockRecorder) DescribeServiceTasks(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeServiceTasks", reflect.TypeOf((*MockAPI)(nil).DescribeServiceTasks), arg0, arg1, arg2, arg3)
|
||||
}
|
||||
|
||||
// DescribeStackEvents mocks base method
|
||||
func (m *MockAPI) DescribeStackEvents(arg0 context.Context, arg1 string) ([]*cloudformation.StackEvent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@ -207,7 +207,7 @@ func (e ecsLocalSimulation) Logs(ctx context.Context, projectName string, consum
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
|
||||
func (e ecsLocalSimulation) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) {
|
||||
return nil, errors.Wrap(errdefs.ErrNotImplemented, "use docker-compose ps")
|
||||
}
|
||||
func (e ecsLocalSimulation) List(ctx context.Context, projectName string) ([]compose.Stack, error) {
|
||||
|
30
ecs/ps.go
30
ecs/ps.go
@ -18,13 +18,11 @@ package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
)
|
||||
|
||||
func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
|
||||
func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.ContainerSummary, error) {
|
||||
cluster, err := b.aws.GetStackClusterID(ctx, project)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -38,23 +36,23 @@ func (b *ecsAPIService) Ps(ctx context.Context, project string) ([]compose.Servi
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
status := []compose.ServiceStatus{}
|
||||
summary := []compose.ContainerSummary{}
|
||||
for _, arn := range servicesARN {
|
||||
state, err := b.aws.DescribeService(ctx, cluster, arn)
|
||||
service, err := b.aws.DescribeService(ctx, cluster, arn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ports := []string{}
|
||||
for _, lb := range state.Publishers {
|
||||
ports = append(ports, fmt.Sprintf(
|
||||
"%s:%d->%d/%s",
|
||||
lb.URL,
|
||||
lb.PublishedPort,
|
||||
lb.TargetPort,
|
||||
strings.ToLower(lb.Protocol)))
|
||||
|
||||
tasks, err := b.aws.DescribeServiceTasks(ctx, cluster, project, service.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state.Ports = ports
|
||||
status = append(status, state)
|
||||
|
||||
for i, t := range tasks {
|
||||
t.Publishers = service.Publishers
|
||||
tasks[i] = t
|
||||
}
|
||||
summary = append(summary, tasks...)
|
||||
}
|
||||
return status, nil
|
||||
return summary, nil
|
||||
}
|
||||
|
67
ecs/sdk.go
67
ecs/sdk.go
@ -819,6 +819,69 @@ func (s sdk) DescribeService(ctx context.Context, cluster string, arn string) (c
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s sdk) DescribeServiceTasks(ctx context.Context, cluster string, project string, service string) ([]compose.ContainerSummary, error) {
|
||||
var summary []compose.ContainerSummary
|
||||
familly := fmt.Sprintf("%s-%s", project, service)
|
||||
var token *string
|
||||
for {
|
||||
list, err := s.ECS.ListTasks(&ecs.ListTasksInput{
|
||||
Cluster: aws.String(cluster),
|
||||
Family: aws.String(familly),
|
||||
LaunchType: nil,
|
||||
MaxResults: nil,
|
||||
NextToken: token,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list.TaskArns) == 0 {
|
||||
break
|
||||
}
|
||||
tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{
|
||||
Cluster: aws.String(cluster),
|
||||
Include: aws.StringSlice([]string{"TAGS"}),
|
||||
Tasks: list.TaskArns,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range tasks.Tasks {
|
||||
var project string
|
||||
var service string
|
||||
for _, tag := range t.Tags {
|
||||
switch aws.StringValue(tag.Key) {
|
||||
case compose.ProjectTag:
|
||||
project = aws.StringValue(tag.Value)
|
||||
case compose.ServiceTag:
|
||||
service = aws.StringValue(tag.Value)
|
||||
}
|
||||
}
|
||||
|
||||
id, err := arn.Parse(aws.StringValue(t.TaskArn))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
summary = append(summary, compose.ContainerSummary{
|
||||
ID: id.String(),
|
||||
Name: id.Resource,
|
||||
Project: project,
|
||||
Service: service,
|
||||
State: strings.Title(strings.ToLower(aws.StringValue(t.LastStatus))),
|
||||
})
|
||||
}
|
||||
|
||||
if list.NextToken == token {
|
||||
break
|
||||
}
|
||||
token = list.NextToken
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func (s sdk) getURLWithPortMapping(ctx context.Context, targetGroupArns []string) ([]compose.PortPublisher, error) {
|
||||
if len(targetGroupArns) == 0 {
|
||||
return nil, nil
|
||||
@ -861,10 +924,10 @@ func (s sdk) getURLWithPortMapping(ctx context.Context, targetGroupArns []string
|
||||
continue
|
||||
}
|
||||
loadBalancers = append(loadBalancers, compose.PortPublisher{
|
||||
URL: aws.StringValue(lb.DNSName),
|
||||
URL: fmt.Sprintf("%s:%d", aws.StringValue(lb.DNSName), aws.Int64Value(tg.Port)),
|
||||
TargetPort: int(aws.Int64Value(tg.Port)),
|
||||
PublishedPort: int(aws.Int64Value(tg.Port)),
|
||||
Protocol: aws.StringValue(tg.Protocol),
|
||||
Protocol: strings.ToLower(aws.StringValue(tg.Protocol)),
|
||||
})
|
||||
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ func (cs *composeService) Down(ctx context.Context, project string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cs *composeService) Ps(ctx context.Context, project string) ([]compose.ServiceStatus, error) {
|
||||
func (cs *composeService) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) {
|
||||
return nil, errdefs.ErrNotImplemented
|
||||
}
|
||||
func (cs *composeService) List(ctx context.Context, project string) ([]compose.Stack, error) {
|
||||
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
composetypes "github.com/compose-spec/compose-go/types"
|
||||
"github.com/docker/docker/api/types"
|
||||
mountTypes "github.com/docker/docker/api/types/mount"
|
||||
"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 TestContainersToServiceStatus(t *testing.T) {
|
||||
containers := []types.Container{
|
||||
{
|
||||
ID: "c1",
|
||||
State: "running",
|
||||
Labels: map[string]string{serviceLabel: "service1"},
|
||||
},
|
||||
{
|
||||
ID: "c2",
|
||||
State: "exited",
|
||||
Labels: map[string]string{serviceLabel: "service1"},
|
||||
},
|
||||
{
|
||||
ID: "c3",
|
||||
State: "running",
|
||||
Labels: map[string]string{serviceLabel: "service1"},
|
||||
},
|
||||
{
|
||||
ID: "c4",
|
||||
State: "running",
|
||||
Labels: map[string]string{serviceLabel: "service2"},
|
||||
},
|
||||
}
|
||||
services, err := containersToServiceStatus(containers)
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, services, []compose.ServiceStatus{
|
||||
{
|
||||
ID: "service1",
|
||||
Name: "service1",
|
||||
Replicas: 2,
|
||||
Desired: 3,
|
||||
},
|
||||
{
|
||||
ID: "service2",
|
||||
Name: "service2",
|
||||
Replicas: 1,
|
||||
Desired: 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)")
|
||||
}
|
||||
|
||||
func TestBuildBindMount(t *testing.T) {
|
||||
project := composetypes.Project{}
|
||||
volume := composetypes.ServiceVolumeConfig{
|
||||
Type: composetypes.VolumeTypeBind,
|
||||
Source: "e2e/volume-test",
|
||||
Target: "/data",
|
||||
}
|
||||
mount, err := buildMount(project, volume)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, filepath.IsAbs(mount.Source))
|
||||
assert.Equal(t, mount.Type, mountTypes.TypeBind)
|
||||
}
|
||||
|
||||
func TestBuildVolumeMount(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Volumes: composetypes.Volumes(map[string]composetypes.VolumeConfig{
|
||||
"myVolume": {
|
||||
Name: "myProject_myVolume",
|
||||
},
|
||||
}),
|
||||
}
|
||||
volume := composetypes.ServiceVolumeConfig{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Source: "myVolume",
|
||||
Target: "/data",
|
||||
}
|
||||
mount, err := buildMount(project, volume)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, mount.Source, "myProject_myVolume")
|
||||
assert.Equal(t, mount.Type, mountTypes.TypeVolume)
|
||||
}
|
62
local/compose/create_test.go
Normal file
62
local/compose/create_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
composetypes "github.com/compose-spec/compose-go/types"
|
||||
mountTypes "github.com/docker/docker/api/types/mount"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestBuildBindMount(t *testing.T) {
|
||||
project := composetypes.Project{}
|
||||
volume := composetypes.ServiceVolumeConfig{
|
||||
Type: composetypes.VolumeTypeBind,
|
||||
Source: "",
|
||||
Target: "/data",
|
||||
}
|
||||
mount, err := buildMount(project, volume)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, filepath.IsAbs(mount.Source))
|
||||
_, err = os.Stat(mount.Source)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, mount.Type, mountTypes.TypeBind)
|
||||
}
|
||||
|
||||
func TestBuildVolumeMount(t *testing.T) {
|
||||
project := composetypes.Project{
|
||||
Name: "myProject",
|
||||
Volumes: composetypes.Volumes(map[string]composetypes.VolumeConfig{
|
||||
"myVolume": {
|
||||
Name: "myProject_myVolume",
|
||||
},
|
||||
}),
|
||||
}
|
||||
volume := composetypes.ServiceVolumeConfig{
|
||||
Type: composetypes.VolumeTypeVolume,
|
||||
Source: "myVolume",
|
||||
Target: "/data",
|
||||
}
|
||||
mount, err := buildMount(project, volume)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, mount.Source, "myProject_myVolume")
|
||||
assert.Equal(t, mount.Type, mountTypes.TypeVolume)
|
||||
}
|
66
local/compose/ls_test.go
Normal file
66
local/compose/ls_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestContainersToStacks(t *testing.T) {
|
||||
containers := []moby.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)")
|
||||
}
|
@ -21,15 +21,13 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
convert "github.com/docker/compose-cli/local/moby"
|
||||
|
||||
"github.com/docker/compose-cli/api/compose"
|
||||
moby "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ServiceStatus, error) {
|
||||
list, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.ContainerSummary, error) {
|
||||
containers, err := s.apiClient.ContainerList(ctx, moby.ContainerListOptions{
|
||||
Filters: filters.NewArgs(
|
||||
projectFilter(projectName),
|
||||
),
|
||||
@ -37,31 +35,33 @@ func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containersToServiceStatus(list)
|
||||
}
|
||||
|
||||
func containersToServiceStatus(containers []moby.Container) ([]compose.ServiceStatus, error) {
|
||||
containersByLabel, keys, err := groupContainerByLabel(containers, serviceLabel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var services []compose.ServiceStatus
|
||||
for _, service := range keys {
|
||||
containers := containersByLabel[service]
|
||||
runnningContainers := []moby.Container{}
|
||||
for _, container := range containers {
|
||||
if container.State == convert.ContainerRunning {
|
||||
runnningContainers = append(runnningContainers, container)
|
||||
var summary []compose.ContainerSummary
|
||||
for _, c := range containers {
|
||||
var publishers []compose.PortPublisher
|
||||
for _, p := range c.Ports {
|
||||
var url string
|
||||
if p.PublicPort != 0 {
|
||||
url = fmt.Sprintf("%s:%d", p.IP, p.PublicPort)
|
||||
}
|
||||
publishers = append(publishers, compose.PortPublisher{
|
||||
URL: url,
|
||||
TargetPort: int(p.PrivatePort),
|
||||
PublishedPort: int(p.PublicPort),
|
||||
Protocol: p.Type,
|
||||
})
|
||||
}
|
||||
services = append(services, compose.ServiceStatus{
|
||||
ID: service,
|
||||
Name: service,
|
||||
Desired: len(containers),
|
||||
Replicas: len(runnningContainers),
|
||||
|
||||
summary = append(summary, compose.ContainerSummary{
|
||||
ID: c.ID,
|
||||
Name: getContainerName(c),
|
||||
Project: c.Labels[projectLabel],
|
||||
Service: c.Labels[serviceLabel],
|
||||
State: c.State,
|
||||
Publishers: publishers,
|
||||
})
|
||||
}
|
||||
return services, nil
|
||||
return summary, nil
|
||||
}
|
||||
|
||||
func groupContainerByLabel(containers []moby.Container, labelName string) (map[string][]moby.Container, []string, error) {
|
||||
|
@ -54,11 +54,12 @@ func (p *proxy) Services(ctx context.Context, request *composev1.ComposeServices
|
||||
}
|
||||
projectName = project.Name
|
||||
}
|
||||
services, err := Client(ctx).ComposeService().Ps(ctx, projectName)
|
||||
response := []*composev1.Service{}
|
||||
_, err := Client(ctx).ComposeService().Ps(ctx, projectName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response := []*composev1.Service{}
|
||||
/* FIXME need to create `docker service ls` command to re-introduce this feature
|
||||
for _, service := range services {
|
||||
response = append(response, &composev1.Service{
|
||||
Id: service.ID,
|
||||
@ -67,7 +68,7 @@ func (p *proxy) Services(ctx context.Context, request *composev1.ComposeServices
|
||||
Desired: uint32(service.Desired),
|
||||
Ports: service.Ports,
|
||||
})
|
||||
}
|
||||
}*/
|
||||
return &composev1.ComposeServicesResponse{Services: response}, nil
|
||||
}
|
||||
|
||||
|
@ -692,7 +692,7 @@ func TestUpUpdate(t *testing.T) {
|
||||
for _, l := range out {
|
||||
if strings.Contains(l, serverContainer) {
|
||||
webRunning = true
|
||||
strings.Contains(l, ":80->80/tcp")
|
||||
assert.Check(t, strings.Contains(l, ":80->80/tcp"))
|
||||
}
|
||||
}
|
||||
assert.Assert(t, webRunning, "web container not running ; ps:\n"+res.Stdout())
|
||||
@ -734,20 +734,23 @@ func TestUpUpdate(t *testing.T) {
|
||||
var wordsDisplayed, webDisplayed, dbDisplayed bool
|
||||
for _, line := range l {
|
||||
fields := strings.Fields(line)
|
||||
containerID := fields[0]
|
||||
switch containerID {
|
||||
name := fields[0]
|
||||
switch name {
|
||||
case wordsContainer:
|
||||
wordsDisplayed = true
|
||||
assert.DeepEqual(t, fields, []string{containerID, "words", "1/1"})
|
||||
assert.Equal(t, fields[2], "Running")
|
||||
case dbContainer:
|
||||
dbDisplayed = true
|
||||
assert.DeepEqual(t, fields, []string{containerID, "db", "1/1"})
|
||||
assert.Equal(t, fields[2], "Running")
|
||||
case serverContainer:
|
||||
webDisplayed = true
|
||||
assert.Equal(t, fields[1], "web")
|
||||
assert.Equal(t, fields[2], "Running")
|
||||
assert.Check(t, strings.Contains(fields[3], ":80->80/tcp"))
|
||||
}
|
||||
}
|
||||
assert.Check(t, webDisplayed, "webDisplayed"+res.Stdout())
|
||||
assert.Check(t, wordsDisplayed, "wordsDisplayed"+res.Stdout())
|
||||
assert.Check(t, dbDisplayed, "dbDisplayed"+res.Stdout())
|
||||
assert.Check(t, webDisplayed && wordsDisplayed && dbDisplayed, "\n%s\n", res.Stdout())
|
||||
})
|
||||
|
||||
|
@ -100,7 +100,7 @@ func TestCompose(t *testing.T) {
|
||||
switch serviceName {
|
||||
case "db":
|
||||
dbDisplayed = true
|
||||
assert.DeepEqual(t, fields, []string{containerID, serviceName, "1/1"})
|
||||
assert.DeepEqual(t, fields, []string{containerID, serviceName, "Running"})
|
||||
case "words":
|
||||
wordsDisplayed = true
|
||||
assert.Check(t, strings.Contains(fields[3], ":8080->8080/tcp"))
|
||||
|
Loading…
x
Reference in New Issue
Block a user