From f892ee1004fc4cdd9a1d78663b33be36d34a8e18 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Fri, 3 Jul 2020 11:07:08 +0200 Subject: [PATCH] `ps` shows LoadBalancer URL Signed-off-by: Nicolas De Loof --- ecs/pkg/amazon/backend/list.go | 28 ++++++++++++++-- ecs/pkg/amazon/backend/up.go | 1 - ecs/pkg/amazon/sdk/api.go | 8 +++-- ecs/pkg/amazon/sdk/sdk.go | 58 +++++++++++++++++++--------------- ecs/pkg/compose/types.go | 7 ++++ 5 files changed, 71 insertions(+), 31 deletions(-) diff --git a/ecs/pkg/amazon/backend/list.go b/ecs/pkg/amazon/backend/list.go index 1193d1fcb..00b4adee3 100644 --- a/ecs/pkg/amazon/backend/list.go +++ b/ecs/pkg/amazon/backend/list.go @@ -14,7 +14,31 @@ func (b *Backend) Ps(ctx context.Context, project *types.Project) ([]compose.Ser cluster = project.Name } - status, err := b.api.DescribeServices(ctx, cluster, project.Name) + resources, err := b.api.ListStackResources(ctx, project.Name) + if err != nil { + return nil, err + } + + var loadBalancer string + if lb, ok := project.Extensions[compose.ExtensionLB]; ok { + loadBalancer = lb.(string) + } + servicesARN := []string{} + for _, r := range resources { + switch r.Type { + case "AWS::ECS::Service": + servicesARN = append(servicesARN, r.ARN) + case "AWS::ElasticLoadBalancingV2::LoadBalancer": + loadBalancer = r.ARN + } + } + + status, err := b.api.DescribeServices(ctx, cluster, servicesARN) + if err != nil { + return nil, err + } + + url, err := b.api.GetLoadBalancerURL(ctx, loadBalancer) if err != nil { return nil, err } @@ -26,7 +50,7 @@ func (b *Backend) Ps(ctx context.Context, project *types.Project) ([]compose.Ser } ports := []string{} for _, p := range s.Ports { - ports = append(ports, fmt.Sprintf("*:%d->%d/%s", p.Published, p.Target, p.Protocol)) + ports = append(ports, fmt.Sprintf("%s:%d->%d/%s", url, p.Published, p.Target, p.Protocol)) } state.Ports = ports status[i] = state diff --git a/ecs/pkg/amazon/backend/up.go b/ecs/pkg/amazon/backend/up.go index fae330e62..104fef915 100644 --- a/ecs/pkg/amazon/backend/up.go +++ b/ecs/pkg/amazon/backend/up.go @@ -105,7 +105,6 @@ func (b Backend) GetLoadBalancer(ctx context.Context, project *types.Project) (s if !ok { return "", fmt.Errorf("Load Balancer does not exist: %s", lb) } - return b.api.GetLoadBalancerARN(ctx, lbName) } return "", nil } diff --git a/ecs/pkg/amazon/sdk/api.go b/ecs/pkg/amazon/sdk/api.go index 38782f5d6..057823c53 100644 --- a/ecs/pkg/amazon/sdk/api.go +++ b/ecs/pkg/amazon/sdk/api.go @@ -16,13 +16,15 @@ type API interface { StackExists(ctx context.Context, name string) (bool, error) CreateStack(ctx context.Context, name string, template *cloudformation.Template, parameters map[string]string) error DeleteStack(ctx context.Context, name string) error - DescribeServices(ctx context.Context, cluster string, project string) ([]compose.ServiceStatus, error) + ListStackResources(ctx context.Context, name string) ([]compose.StackResource, error) GetStackID(ctx context.Context, name string) (string, error) WaitStackComplete(ctx context.Context, name string, operation int) error DescribeStackEvents(ctx context.Context, stackID string) ([]*cf.StackEvent, error) - LoadBalancerExists(ctx context.Context, name string) (bool, error) - GetLoadBalancerARN(ctx context.Context, name string) (string, error) + DescribeServices(ctx context.Context, cluster string, arns []string) ([]compose.ServiceStatus, error) + + LoadBalancerExists(ctx context.Context, arn string) (bool, error) + GetLoadBalancerURL(ctx context.Context, arn string) (string, error) ClusterExists(ctx context.Context, name string) (bool, error) diff --git a/ecs/pkg/amazon/sdk/sdk.go b/ecs/pkg/amazon/sdk/sdk.go index af38523b7..5874a8009 100644 --- a/ecs/pkg/amazon/sdk/sdk.go +++ b/ecs/pkg/amazon/sdk/sdk.go @@ -222,6 +222,27 @@ func (s sdk) DescribeStackEvents(ctx context.Context, stackID string) ([]*cloudf } } +func (s sdk) ListStackResources(ctx context.Context, name string) ([]compose.StackResource, error) { + // FIXME handle pagination + res, err := s.CF.ListStackResourcesWithContext(ctx, &cloudformation.ListStackResourcesInput{ + StackName: aws.String(name), + }) + if err != nil { + return nil, err + } + + resources := []compose.StackResource{} + for _, r := range res.StackResourceSummaries { + resources = append(resources, compose.StackResource{ + LogicalID: *r.LogicalResourceId, + Type: *r.ResourceType, + ARN: *r.PhysicalResourceId, + Status: *r.ResourceStatus, + }) + } + return resources, nil +} + func (s sdk) DeleteStack(ctx context.Context, name string) error { logrus.Debug("Delete CloudFormation stack") _, err := s.CF.DeleteStackWithContext(ctx, &cloudformation.DeleteStackInput{ @@ -270,7 +291,6 @@ func (s sdk) InspectSecret(ctx context.Context, id string) (compose.Secret, erro } func (s sdk) ListSecrets(ctx context.Context) ([]compose.Secret, error) { - logrus.Debug("List secrets ...") response, err := s.SM.ListSecrets(&secretsmanager.ListSecretsInput{}) if err != nil { @@ -336,18 +356,10 @@ func (s sdk) GetLogs(ctx context.Context, name string, consumer compose.LogConsu } } -func (s sdk) DescribeServices(ctx context.Context, cluster string, project string) ([]compose.ServiceStatus, error) { - // TODO handle pagination - list, err := s.ECS.ListServicesWithContext(ctx, &ecs.ListServicesInput{ - Cluster: aws.String(cluster), - }) - if err != nil { - return nil, err - } - +func (s sdk) DescribeServices(ctx context.Context, cluster string, arns []string) ([]compose.ServiceStatus, error) { services, err := s.ECS.DescribeServicesWithContext(ctx, &ecs.DescribeServicesInput{ Cluster: aws.String(cluster), - Services: list.ServiceArns, + Services: aws.StringSlice(arns), Include: aws.StringSlice([]string{"TAGS"}), }) if err != nil { @@ -356,17 +368,13 @@ func (s sdk) DescribeServices(ctx context.Context, cluster string, project strin status := []compose.ServiceStatus{} for _, service := range services.Services { var name string - var stack string for _, t := range service.Tags { - switch *t.Key { - case compose.ProjectTag: - stack = *t.Value - case compose.ServiceTag: + if *t.Key == compose.ServiceTag { name = *t.Value } } - if stack != project { - continue + if name == "" { + return nil, fmt.Errorf("service %s doesn't have a %s tag", *service.ServiceArn, compose.ServiceTag) } status = append(status, compose.ServiceStatus{ ID: *service.ServiceName, @@ -410,10 +418,10 @@ func (s sdk) GetPublicIPs(ctx context.Context, interfaces ...string) (map[string return publicIPs, nil } -func (s sdk) LoadBalancerExists(ctx context.Context, name string) (bool, error) { - logrus.Debug("Check if cluster was already created: ", name) +func (s sdk) LoadBalancerExists(ctx context.Context, arn string) (bool, error) { + logrus.Debug("Check if LoadBalancer exists: ", arn) lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ - Names: []*string{aws.String(name)}, + LoadBalancerArns: []*string{aws.String(arn)}, }) if err != nil { return false, err @@ -421,13 +429,13 @@ func (s sdk) LoadBalancerExists(ctx context.Context, name string) (bool, error) return len(lbs.LoadBalancers) > 0, nil } -func (s sdk) GetLoadBalancerARN(ctx context.Context, name string) (string, error) { - logrus.Debug("Check if cluster was already created: ", name) +func (s sdk) GetLoadBalancerURL(ctx context.Context, arn string) (string, error) { + logrus.Debug("Retrieve load balancer URL: ", arn) lbs, err := s.ELB.DescribeLoadBalancersWithContext(ctx, &elbv2.DescribeLoadBalancersInput{ - Names: []*string{aws.String(name)}, + LoadBalancerArns: []*string{aws.String(arn)}, }) if err != nil { return "", err } - return *lbs.LoadBalancers[0].LoadBalancerArn, nil + return *lbs.LoadBalancers[0].DNSName, nil } diff --git a/ecs/pkg/compose/types.go b/ecs/pkg/compose/types.go index ae56e65ea..370bfc4b5 100644 --- a/ecs/pkg/compose/types.go +++ b/ecs/pkg/compose/types.go @@ -2,6 +2,13 @@ package compose import "encoding/json" +type StackResource struct { + LogicalID string + Type string + ARN string + Status string +} + type ServiceStatus struct { ID string Name string