`Ps` return ContainerSummary, not Services

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-12-07 14:46:36 +01:00
parent 15040f8473
commit 1d859dc807
No known key found for this signature in database
GPG Key ID: 9858809D6F8F6E7E
16 changed files with 192 additions and 67 deletions

View File

@ -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,29 @@ 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),
})
}
res = append(res, compose.ContainerSummary{
ID: *container.Name,
Name: *container.Name,
Project: project,
Service: *container.Name,
State: convert.GetStatus(container, group),
Publishers: publishers,
})
}
return res, nil
}

View File

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

View File

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

View File

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

View File

@ -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\n", container.Name, 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", "STATE", "PORTS")
}

View File

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

View File

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

View File

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

View File

@ -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
}
return status, nil
summary = append(summary, tasks...)
}
return summary, nil
}

View File

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

View File

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

View File

@ -0,0 +1 @@
package compose

1
local/compose/ls_test.go Normal file
View File

@ -0,0 +1 @@
package compose

View File

@ -28,8 +28,8 @@ import (
"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,7 +37,33 @@ func (s *composeService) Ps(ctx context.Context, projectName string) ([]compose.
if err != nil {
return nil, err
}
return containersToServiceStatus(list)
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,
})
}
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 summary, nil
}
func containersToServiceStatus(containers []moby.Container) ([]compose.ServiceStatus, error) {

View File

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

View File

@ -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())
@ -738,10 +738,10 @@ func TestUpUpdate(t *testing.T) {
switch containerID {
case wordsContainer:
wordsDisplayed = true
assert.DeepEqual(t, fields, []string{containerID, "words", "1/1"})
assert.DeepEqual(t, fields, []string{containerID, "words", "Running"})
case dbContainer:
dbDisplayed = true
assert.DeepEqual(t, fields, []string{containerID, "db", "1/1"})
assert.DeepEqual(t, fields, []string{containerID, "db", "Running"})
case serverContainer:
webDisplayed = true
assert.Equal(t, fields[1], "web")