소스 검색

`Ps` return ContainerSummary, not Services

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 5 년 전
부모
커밋
1d859dc807
16개의 변경된 파일192개의 추가작업 그리고 67개의 파일을 삭제
  1. 21 3
      aci/compose.go
  2. 4 3
      aci/convert/convert.go
  3. 1 1
      api/client/compose.go
  4. 11 1
      api/compose/api.go
  5. 20 30
      cli/cmd/compose/ps.go
  6. 1 0
      ecs/aws.go
  7. 15 0
      ecs/aws_mock.go
  8. 1 1
      ecs/local/compose.go
  9. 14 16
      ecs/ps.go
  10. 65 2
      ecs/sdk.go
  11. 1 1
      example/backend.go
  12. 1 0
      local/compose/create_test.go
  13. 1 0
      local/compose/ls_test.go
  14. 29 3
      local/compose/ps.go
  15. 4 3
      server/proxy/compose.go
  16. 3 3
      tests/aci-e2e/e2e-aci_test.go

+ 21 - 3
aci/compose.go

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

+ 4 - 3
aci/convert/convert.go

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

+ 1 - 1
api/client/compose.go

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

+ 11 - 1
api/compose/api.go

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

+ 20 - 30
cli/cmd/compose/ps.go

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

+ 1 - 0
ecs/aws.go

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

+ 15 - 0
ecs/aws_mock.go

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

+ 1 - 1
ecs/local/compose.go

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

+ 14 - 16
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
+		}
+
+		for i, t := range tasks {
+			t.Publishers = service.Publishers
+			tasks[i] = t
 		}
-		state.Ports = ports
-		status = append(status, state)
+		summary = append(summary, tasks...)
 	}
-	return status, nil
+	return summary, nil
 }

+ 65 - 2
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:   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)),
 			})
 
 		}

+ 1 - 1
example/backend.go

@@ -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 - 0
local/compose/create_test.go

@@ -0,0 +1 @@
+package compose

+ 1 - 0
local/compose/ls_test.go

@@ -0,0 +1 @@
+package compose

+ 29 - 3
local/compose/ps.go

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

+ 4 - 3
server/proxy/compose.go

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

+ 3 - 3
tests/aci-e2e/e2e-aci_test.go

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