From a1eba59a467578b807b23d7a274252f86203dc65 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 17 Jun 2020 10:20:12 +0200 Subject: [PATCH] `ps` do list services, not containers Signed-off-by: Nicolas De Loof --- ecs/cmd/commands/compose.go | 10 +- ecs/pkg/amazon/backend/cloudformation.go | 3 +- ecs/pkg/amazon/{sdk => backend}/convert.go | 2 +- ecs/pkg/amazon/backend/list.go | 52 ++------ .../simple-cloudformation-conversion.golden | 1 - ...formation-with-overrides-conversion.golden | 1 - ecs/pkg/amazon/sdk/api.go | 4 +- ecs/pkg/amazon/sdk/api_mock.go | 118 ++++++------------ ecs/pkg/amazon/sdk/sdk.go | 51 +++----- ecs/pkg/compose/api.go | 2 +- ecs/pkg/compose/types.go | 13 +- ecs/tests/e2e_deploy_services_test.go | 11 +- 12 files changed, 91 insertions(+), 177 deletions(-) rename ecs/pkg/amazon/{sdk => backend}/convert.go (99%) diff --git a/ecs/cmd/commands/compose.go b/ecs/cmd/commands/compose.go index b3489baee..ed488a891 100644 --- a/ecs/cmd/commands/compose.go +++ b/ecs/cmd/commands/compose.go @@ -101,15 +101,15 @@ func PsCommand(dockerCli command.Cli, projectOpts *cli.ProjectOptions) *cobra.Co if err != nil { return err } - tasks, err := backend.Ps(context.Background(), project) + status, err := backend.Ps(context.Background(), project) if err != nil { return err } - printSection(os.Stdout, len(tasks), func(w io.Writer) { - for _, task := range tasks { - fmt.Fprintf(w, "%s\t%s\t%s\n", task.Name, task.State, strings.Join(task.Ports, " ")) + printSection(os.Stdout, len(status), func(w io.Writer) { + for _, service := range status { + fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, " ")) } - }, "NAME", "STATE", "PORTS") + }, "ID", "NAME", "REPLICAS", "PORTS") return nil }), } diff --git a/ecs/pkg/amazon/backend/cloudformation.go b/ecs/pkg/amazon/backend/cloudformation.go index c27f65317..f4111240b 100644 --- a/ecs/pkg/amazon/backend/cloudformation.go +++ b/ecs/pkg/amazon/backend/cloudformation.go @@ -18,7 +18,6 @@ import ( "github.com/awslabs/goformation/v4/cloudformation/tags" "github.com/compose-spec/compose-go/compatibility" "github.com/compose-spec/compose-go/types" - sdk "github.com/docker/ecs-plugin/pkg/amazon/sdk" "github.com/docker/ecs-plugin/pkg/compose" "github.com/sirupsen/logrus" ) @@ -120,7 +119,7 @@ func (b Backend) Convert(project *types.Project) (*cloudformation.Template, erro for _, service := range project.Services { - definition, err := sdk.Convert(project, service) + definition, err := Convert(project, service) if err != nil { return nil, err } diff --git a/ecs/pkg/amazon/sdk/convert.go b/ecs/pkg/amazon/backend/convert.go similarity index 99% rename from ecs/pkg/amazon/sdk/convert.go rename to ecs/pkg/amazon/backend/convert.go index 3e2b861f8..0eace3a69 100644 --- a/ecs/pkg/amazon/sdk/convert.go +++ b/ecs/pkg/amazon/backend/convert.go @@ -1,4 +1,4 @@ -package sdk +package backend import ( "fmt" diff --git a/ecs/pkg/amazon/backend/list.go b/ecs/pkg/amazon/backend/list.go index 4385eae55..c13d2f903 100644 --- a/ecs/pkg/amazon/backend/list.go +++ b/ecs/pkg/amazon/backend/list.go @@ -3,61 +3,29 @@ package backend import ( "context" "fmt" - "sort" - "strings" "github.com/compose-spec/compose-go/types" "github.com/docker/ecs-plugin/pkg/compose" ) -func (b *Backend) Ps(ctx context.Context, project *types.Project) ([]compose.TaskStatus, error) { +func (b *Backend) Ps(ctx context.Context, project *types.Project) ([]compose.ServiceStatus, error) { cluster := b.Cluster if cluster == "" { cluster = project.Name } - arns := []string{} + + status := []compose.ServiceStatus{} for _, service := range project.Services { - tasks, err := b.api.ListTasks(ctx, cluster, service.Name) + desc, err := b.api.DescribeService(ctx, cluster, service.Name) if err != nil { - return []compose.TaskStatus{}, err + return nil, err } - arns = append(arns, tasks...) - } - if len(arns) == 0 { - return []compose.TaskStatus{}, nil - } - - tasks, err := b.api.DescribeTasks(ctx, cluster, arns...) - if err != nil { - return []compose.TaskStatus{}, err - } - - networkInterfaces := []string{} - for _, t := range tasks { - if t.NetworkInterface != "" { - networkInterfaces = append(networkInterfaces, t.NetworkInterface) - } - } - publicIps, err := b.api.GetPublicIPs(ctx, networkInterfaces...) - if err != nil { - return []compose.TaskStatus{}, err - } - - sort.Slice(tasks, func(i, j int) bool { - return strings.Compare(tasks[i].Service, tasks[j].Service) < 0 - }) - - for i, task := range tasks { ports := []string{} - s, err := project.GetService(task.Service) - if err != nil { - return []compose.TaskStatus{}, err + for _, p := range service.Ports { + ports = append(ports, fmt.Sprintf("*:%d->%d/%s", p.Published, p.Target, p.Protocol)) } - for _, p := range s.Ports { - ports = append(ports, fmt.Sprintf("%s:%d->%d/%s", publicIps[task.NetworkInterface], p.Published, p.Target, p.Protocol)) - } - tasks[i].Name = s.Name - tasks[i].Ports = ports + desc.Ports = ports + status = append(status, desc) } - return tasks, nil + return status, nil } diff --git a/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-conversion.golden b/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-conversion.golden index 0003b38e5..b9fd6a045 100644 --- a/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-conversion.golden +++ b/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-conversion.golden @@ -104,7 +104,6 @@ } }, "SchedulingStrategy": "REPLICA", - "ServiceName": "simple", "ServiceRegistries": [ { "RegistryArn": { diff --git a/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-with-overrides-conversion.golden b/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-with-overrides-conversion.golden index 07c47765b..0f93cbd54 100644 --- a/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-with-overrides-conversion.golden +++ b/ecs/pkg/amazon/backend/testdata/simple/simple-cloudformation-with-overrides-conversion.golden @@ -104,7 +104,6 @@ } }, "SchedulingStrategy": "REPLICA", - "ServiceName": "simple", "ServiceRegistries": [ { "RegistryArn": { diff --git a/ecs/pkg/amazon/sdk/api.go b/ecs/pkg/amazon/sdk/api.go index feaa1eb77..1ad68afa8 100644 --- a/ecs/pkg/amazon/sdk/api.go +++ b/ecs/pkg/amazon/sdk/api.go @@ -49,9 +49,7 @@ type secretsAPI interface { } type listAPI interface { - ListTasks(ctx context.Context, cluster string, name string) ([]string, error) - DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]compose.TaskStatus, error) - GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) + DescribeService(ctx context.Context, cluster string, name string) (compose.ServiceStatus, error) } type waitAPI interface { diff --git a/ecs/pkg/amazon/sdk/api_mock.go b/ecs/pkg/amazon/sdk/api_mock.go index 0fe42fb2f..dda73b715 100644 --- a/ecs/pkg/amazon/sdk/api_mock.go +++ b/ecs/pkg/amazon/sdk/api_mock.go @@ -1,18 +1,16 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/docker/ecs-plugin/pkg/amazon (interfaces: API) +// Source: github.com/docker/ecs-plugin/pkg/amazon/sdk (interfaces: API) -// Package amazon is a generated GoMock package. +// Package sdk is a generated GoMock package. package sdk import ( context "context" - reflect "reflect" - - "github.com/docker/ecs-plugin/pkg/compose" - cloudformation "github.com/aws/aws-sdk-go/service/cloudformation" cloudformation0 "github.com/awslabs/goformation/v4/cloudformation" + compose "github.com/docker/ecs-plugin/pkg/compose" gomock "github.com/golang/mock/gomock" + reflect "reflect" ) // MockAPI is a mock of API interface @@ -124,6 +122,21 @@ func (mr *MockAPIMockRecorder) DeleteStack(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteStack", reflect.TypeOf((*MockAPI)(nil).DeleteStack), arg0, arg1) } +// DescribeService mocks base method +func (m *MockAPI) DescribeService(arg0 context.Context, arg1, arg2 string) (compose.ServiceStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DescribeService", arg0, arg1, arg2) + ret0, _ := ret[0].(compose.ServiceStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DescribeService indicates an expected call of DescribeService +func (mr *MockAPIMockRecorder) DescribeService(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeService", reflect.TypeOf((*MockAPI)(nil).DescribeService), arg0, arg1, arg2) +} + // DescribeStackEvents mocks base method func (m *MockAPI) DescribeStackEvents(arg0 context.Context, arg1 string) ([]*cloudformation.StackEvent, error) { m.ctrl.T.Helper() @@ -139,26 +152,6 @@ func (mr *MockAPIMockRecorder) DescribeStackEvents(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeStackEvents", reflect.TypeOf((*MockAPI)(nil).DescribeStackEvents), arg0, arg1) } -// DescribeTasks mocks base method -func (m *MockAPI) DescribeTasks(arg0 context.Context, arg1 string, arg2 ...string) ([]compose.TaskStatus, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0, arg1} - for _, a := range arg2 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "DescribeTasks", varargs...) - ret0, _ := ret[0].([]compose.TaskStatus) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DescribeTasks indicates an expected call of DescribeTasks -func (mr *MockAPIMockRecorder) DescribeTasks(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0, arg1}, arg2...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DescribeTasks", reflect.TypeOf((*MockAPI)(nil).DescribeTasks), varargs...) -} - // GetDefaultVPC mocks base method func (m *MockAPI) GetDefaultVPC(arg0 context.Context) (string, error) { m.ctrl.T.Helper() @@ -174,6 +167,21 @@ func (mr *MockAPIMockRecorder) GetDefaultVPC(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultVPC", reflect.TypeOf((*MockAPI)(nil).GetDefaultVPC), arg0) } +// GetLoadBalancerARN mocks base method +func (m *MockAPI) GetLoadBalancerARN(arg0 context.Context, arg1 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLoadBalancerARN", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLoadBalancerARN indicates an expected call of GetLoadBalancerARN +func (mr *MockAPIMockRecorder) GetLoadBalancerARN(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancerARN", reflect.TypeOf((*MockAPI)(nil).GetLoadBalancerARN), arg0, arg1) +} + // GetLogs mocks base method func (m *MockAPI) GetLogs(arg0 context.Context, arg1 string, arg2 compose.LogConsumer) error { m.ctrl.T.Helper() @@ -188,26 +196,6 @@ func (mr *MockAPIMockRecorder) GetLogs(arg0, arg1, arg2 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockAPI)(nil).GetLogs), arg0, arg1, arg2) } -// GetPublicIPs mocks base method -func (m *MockAPI) GetPublicIPs(arg0 context.Context, arg1 ...string) (map[string]string, error) { - m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetPublicIPs", varargs...) - ret0, _ := ret[0].(map[string]string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetPublicIPs indicates an expected call of GetPublicIPs -func (mr *MockAPIMockRecorder) GetPublicIPs(arg0 interface{}, arg1 ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicIPs", reflect.TypeOf((*MockAPI)(nil).GetPublicIPs), varargs...) -} - // GetStackID mocks base method func (m *MockAPI) GetStackID(arg0 context.Context, arg1 string) (string, error) { m.ctrl.T.Helper() @@ -268,19 +256,19 @@ func (mr *MockAPIMockRecorder) ListSecrets(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSecrets", reflect.TypeOf((*MockAPI)(nil).ListSecrets), arg0) } -// ListTasks mocks base method -func (m *MockAPI) ListTasks(arg0 context.Context, arg1, arg2 string) ([]string, error) { +// LoadBalancerExists mocks base method +func (m *MockAPI) LoadBalancerExists(arg0 context.Context, arg1 string) (bool, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListTasks", arg0, arg1, arg2) - ret0, _ := ret[0].([]string) + ret := m.ctrl.Call(m, "LoadBalancerExists", arg0, arg1) + ret0, _ := ret[0].(bool) ret1, _ := ret[1].(error) return ret0, ret1 } -// ListTasks indicates an expected call of ListTasks -func (mr *MockAPIMockRecorder) ListTasks(arg0, arg1, arg2 interface{}) *gomock.Call { +// LoadBalancerExists indicates an expected call of LoadBalancerExists +func (mr *MockAPIMockRecorder) LoadBalancerExists(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListTasks", reflect.TypeOf((*MockAPI)(nil).ListTasks), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerExists", reflect.TypeOf((*MockAPI)(nil).LoadBalancerExists), arg0, arg1) } // StackExists mocks base method @@ -326,27 +314,3 @@ func (mr *MockAPIMockRecorder) WaitStackComplete(arg0, arg1, arg2 interface{}) * mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WaitStackComplete", reflect.TypeOf((*MockAPI)(nil).WaitStackComplete), arg0, arg1, arg2) } - -// LoadBalancerExists mocks base method -func (m *MockAPI) LoadBalancerExists(arg0 context.Context, arg1 string) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "LoadBalancerExists", arg0, arg1) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// LoadBalancerExists indicates an expected call of VpcExists -func (mr *MockAPIMockRecorder) LoadBalancerExists(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerExists", reflect.TypeOf((*MockAPI)(nil).LoadBalancerExists), arg0, arg1) -} - -// GetLoadBalancerARN mocks base method -func (m *MockAPI) GetLoadBalancerARN(arg0 context.Context, arg1 string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLoadBalancerARN", arg0) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} diff --git a/ecs/pkg/amazon/sdk/sdk.go b/ecs/pkg/amazon/sdk/sdk.go index 4d8828697..3cc2c0fa0 100644 --- a/ecs/pkg/amazon/sdk/sdk.go +++ b/ecs/pkg/amazon/sdk/sdk.go @@ -341,10 +341,26 @@ func (s sdk) GetLogs(ctx context.Context, name string, consumer compose.LogConsu } } -func (s sdk) ListTasks(ctx context.Context, cluster string, service string) ([]string, error) { +func (s sdk) DescribeService(ctx context.Context, cluster string, name string) (compose.ServiceStatus, error) { + services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{ + Cluster: aws.String(cluster), + Services: aws.StringSlice([]string{name}), + }) + if err != nil { + return compose.ServiceStatus{}, err + } + return compose.ServiceStatus{ + ID: *services.Services[0].ServiceName, + Name: name, + Replicas: int(*services.Services[0].RunningCount), + Desired: int(*services.Services[0].DesiredCount), + }, nil +} + +func (s sdk) ListTasks(ctx context.Context, cluster string, family string) ([]string, error) { tasks, err := s.ECS.ListTasksWithContext(ctx, &ecs.ListTasksInput{ - Cluster: aws.String(cluster), - ServiceName: aws.String(service), + Cluster: aws.String(cluster), + Family: aws.String(family), }) if err != nil { return nil, err @@ -356,35 +372,6 @@ func (s sdk) ListTasks(ctx context.Context, cluster string, service string) ([]s return arns, nil } -func (s sdk) DescribeTasks(ctx context.Context, cluster string, arns ...string) ([]compose.TaskStatus, error) { - tasks, err := s.ECS.DescribeTasksWithContext(ctx, &ecs.DescribeTasksInput{ - Cluster: aws.String(cluster), - Tasks: aws.StringSlice(arns), - }) - if err != nil { - return nil, err - } - result := []compose.TaskStatus{} - for _, task := range tasks.Tasks { - var networkInterface string - for _, attachement := range task.Attachments { - if *attachement.Type == "ElasticNetworkInterface" { - for _, pair := range attachement.Details { - if *pair.Name == "networkInterfaceId" { - networkInterface = *pair.Value - } - } - } - } - result = append(result, compose.TaskStatus{ - State: *task.LastStatus, - Service: strings.Replace(*task.Group, "service:", "", 1), - NetworkInterface: networkInterface, - }) - } - return result, nil -} - func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string]string, error) { desc, err := s.EC2.DescribeNetworkInterfaces(&ec2.DescribeNetworkInterfacesInput{ NetworkInterfaceIds: aws.StringSlice(interfaces), diff --git a/ecs/pkg/compose/api.go b/ecs/pkg/compose/api.go index 0d4877a8b..64e7e5c87 100644 --- a/ecs/pkg/compose/api.go +++ b/ecs/pkg/compose/api.go @@ -14,7 +14,7 @@ type API interface { Convert(project *types.Project) (*cloudformation.Template, error) Logs(ctx context.Context, projectName string) error - Ps(background context.Context, project *types.Project) ([]TaskStatus, error) + Ps(background context.Context, project *types.Project) ([]ServiceStatus, error) CreateSecret(ctx context.Context, secret Secret) (string, error) InspectSecret(ctx context.Context, id string) (Secret, error) diff --git a/ecs/pkg/compose/types.go b/ecs/pkg/compose/types.go index 133263658..ae56e65ea 100644 --- a/ecs/pkg/compose/types.go +++ b/ecs/pkg/compose/types.go @@ -2,13 +2,12 @@ package compose import "encoding/json" -type TaskStatus struct { - Name string - State string - Service string - NetworkInterface string - PublicIP string - Ports []string +type ServiceStatus struct { + ID string + Name string + Replicas int + Desired int + Ports []string } const ( diff --git a/ecs/tests/e2e_deploy_services_test.go b/ecs/tests/e2e_deploy_services_test.go index c8a2242ce..e55fc93be 100644 --- a/ecs/tests/e2e_deploy_services_test.go +++ b/ecs/tests/e2e_deploy_services_test.go @@ -6,9 +6,10 @@ import ( "context" "testing" + "github.com/docker/ecs-plugin/pkg/amazon/sdk" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" - "github.com/docker/ecs-plugin/pkg/amazon" "github.com/docker/ecs-plugin/pkg/docker" "gotest.tools/v3/assert" "gotest.tools/v3/fs" @@ -47,11 +48,11 @@ func composeUpSimpleService(t *testing.T, cmd icmd.Cmd, awsContext docker.AwsCon }, }) assert.NilError(t, err) - sdk := amazon.NewAPI(session) - arns, err := sdk.ListTasks(bgContext, t.Name(), t.Name()) + api := sdk.NewAPI(session) + arns, err := api.ListTasks(bgContext, t.Name(), t.Name()) assert.NilError(t, err) - tasks, err := sdk.DescribeTasks(bgContext, t.Name(), arns...) - publicIps, err := sdk.GetPublicIPs(context.Background(), tasks[0].NetworkInterface) + tasks, err := api.DescribeTasks(bgContext, t.Name(), arns...) + publicIps, err := api.GetPublicIPs(context.Background(), tasks[0].NetworkInterface) assert.NilError(t, err) for _, ip := range publicIps { icmd.RunCommand("curl", "-I", "http://"+ip).Assert(t, icmd.Success)