Skip to content
This repository has been archived by the owner on Jan 8, 2024. It is now read-only.

Allow bringing your own alb listener #4453

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/4453.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
plugin/aws-ecs: Fix bringing your own alb listener to ecs deployments.
```
29 changes: 23 additions & 6 deletions builtin/aws/ecs/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,18 @@ func (p *Platform) Deploy(
result.TargetGroupArn = tgState.Arn

albState := rm.Resource("application load balancer").State().(*Resource_Alb)
result.LoadBalancerArn = albState.Arn
albListenerState := rm.Resource("alb listener").State().(*Resource_Alb_Listener)
if albState != nil && albState.Arn != "" {
result.LbReference = &Deployment_LoadBalancerArn{
LoadBalancerArn: albState.Arn,
}
} else if albListenerState != nil && albListenerState.Arn != "" {
result.LbReference = &Deployment_ListenerArn{
ListenerArn: albListenerState.Arn,
}
} else {
return nil, status.Errorf(codes.FailedPrecondition, "missing an alb and an alb listener - one must be set")
}

cState := rm.Resource("cluster").State().(*Resource_Cluster)
result.Cluster = cState.Name
Expand Down Expand Up @@ -1720,7 +1731,6 @@ func (p *Platform) resourceAlbCreate(
}
state.Arn = *lb.LoadBalancerArn

state.Arn = *lb.LoadBalancerArn
state.DnsName = *lb.DNSName
state.CanonicalHostedZoneId = *lb.CanonicalHostedZoneId

Expand Down Expand Up @@ -2514,22 +2524,29 @@ func (p *Platform) loadResourceManagerState(
listenerResource.Managed = false
log.Debug("Using existing listener", "arn", listenerResource.Arn)
} else {
var loadBalancerArn string
if albRef, ok := deployment.LbReference.(*Deployment_LoadBalancerArn); ok {
loadBalancerArn = albRef.LoadBalancerArn
} else {
return status.Errorf(codes.FailedPrecondition, "cannot restore state to release this old deployment - expecting deployment to have an ALB reference, instead got %T. Please deploy again.", deployment.LbReference)
}

listenerResource.Managed = true
s.Update("Describing load balancer %s", deployment.LoadBalancerArn)
s.Update("Describing load balancer %s", loadBalancerArn)
sess, err := p.getSession(log)
if err != nil {
return status.Errorf(codes.Internal, "failed to get aws session: %s", err)
}
elbsrv := elbv2.New(sess)

listeners, err := elbsrv.DescribeListenersWithContext(ctx, &elbv2.DescribeListenersInput{
LoadBalancerArn: &deployment.LoadBalancerArn,
LoadBalancerArn: &loadBalancerArn,
})
if err != nil {
return status.Errorf(codes.Internal, "failed to describe listeners for ALB %q: %s", deployment.LoadBalancerArn, err)
return status.Errorf(codes.Internal, "failed to describe listeners for ALB %q: %s", loadBalancerArn, err)
}
if len(listeners.Listeners) == 0 {
s.Update("No listeners found for ALB %q", deployment.LoadBalancerArn)
s.Update("No listeners found for ALB %q", loadBalancerArn)
} else {
listenerResource.Arn = *listeners.Listeners[0].ListenerArn
s.Update("Found existing listener (ARN: %q)", listenerResource.Arn)
Expand Down
252 changes: 148 additions & 104 deletions builtin/aws/ecs/plugin.pb.go

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion builtin/aws/ecs/plugin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ message Deployment {
string task_arn = 2;
string service_arn = 3;
string target_group_arn = 4;
string load_balancer_arn = 5;

// Reference to the load balancer. Either the LB itself (if we created it),
// or the lb listener if the user specified it.
oneof lb_reference {
string load_balancer_arn = 5;
string listener_arn = 8;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing logic, a user could specify a listener arn in the platform config, but we weren't making that available to the releaser.

}
Comment on lines +17 to +20
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it could be a breaking protobuf change, but I don't think it is! It's not changing field numbers. I also tried releasing a deployment made with the previous compiled protobufs, and it worked.


string cluster = 6;
opaqueany.Any resource_state = 7;
}
Expand Down
173 changes: 112 additions & 61 deletions builtin/aws/ecs/releaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/hashicorp/go-hclog"
"github.com/pkg/errors"

"github.com/hashicorp/waypoint-plugin-sdk/component"
"github.com/hashicorp/waypoint-plugin-sdk/docs"
"github.com/hashicorp/waypoint-plugin-sdk/terminal"
Expand Down Expand Up @@ -37,8 +39,9 @@ func (r *Releaser) Release(
ui terminal.UI,
target *Deployment,
) (*Release, error) {
if target.LoadBalancerArn == "" && target.TargetGroupArn == "" {
log.Info("No load-balancer configured")

if target.TargetGroupArn == "" {
log.Info("No target group configured, skipping release")
return &Release{}, nil
}

Expand All @@ -51,43 +54,119 @@ func (r *Releaser) Release(
}
elbsrv := elbv2.New(sess)

dlb, err := elbsrv.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{&target.LoadBalancerArn},
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the bug. If you specify your own target group arn, target.LoadBalancerArn would be an empty string, and you'd get a 400 on this call.

if err != nil {
return nil, err
}

var lb *elbv2.LoadBalancer

if len(dlb.LoadBalancers) == 0 {
return nil, fmt.Errorf("No load balancers returned by DescribeLoadBalancers")
}

lb = dlb.LoadBalancers[0]

listeners, err := elbsrv.DescribeListeners(&elbv2.DescribeListenersInput{
LoadBalancerArn: lb.LoadBalancerArn,
})
if err != nil {
return nil, err
var hostname string
if r.p.config.ALB != nil && r.p.config.ALB.FQDN != "" {
hostname = r.p.config.ALB.FQDN
}

var listener *elbv2.Listener

tgs := []*elbv2.TargetGroupTuple{
{
TargetGroupArn: &target.TargetGroupArn,
Weight: aws.Int64(100),
},
}

log.Debug("configuring weight 100 for target group", "arn", target.TargetGroupArn)
// existingListener, if discovered, will be modified to introduce the new target group.
var existingListener *elbv2.Listener
var lbArn string

switch lbRef := target.LbReference.(type) {
case *Deployment_LoadBalancerArn:
// We have a load balancer. Either discover the existing listener, or create a new one.
lbArn = lbRef.LoadBalancerArn

dlb, err := elbsrv.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{&lbRef.LoadBalancerArn},
})
if err != nil {
return nil, errors.Wrapf(err, "failed to describe load balancer %q", lbRef.LoadBalancerArn)
}

var lb *elbv2.LoadBalancer

if len(dlb.LoadBalancers) == 0 {
return nil, fmt.Errorf("no load balancers returned by DescribeLoadBalancers")
}

if len(listeners.Listeners) > 0 {
listener = listeners.Listeners[0]
lb = dlb.LoadBalancers[0]

def := listener.DefaultActions
// Now that we have the LB, set the hostname if necessary
if hostname == "" {
hostname = *lb.DNSName
}

listeners, err := elbsrv.DescribeListeners(&elbv2.DescribeListenersInput{
LoadBalancerArn: lb.LoadBalancerArn,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to describe listener for lb %q", lb.LoadBalancerArn)
}

if len(listeners.Listeners) > 0 {
if len(listeners.Listeners) > 1 {
log.Warn("ALB has multiple listeners - only modifying the first listaner and ignoring all others")
}
existingListener = listeners.Listeners[0]
} else {
log.Info("load-balancer defined", "dns-name", *lb.DNSName)

_, err := elbsrv.CreateListener(&elbv2.CreateListenerInput{
LoadBalancerArn: lb.LoadBalancerArn,
Port: aws.Int64(80),
Protocol: aws.String("HTTP"),
DefaultActions: []*elbv2.Action{
{
ForwardConfig: &elbv2.ForwardActionConfig{
TargetGroups: tgs,
},
Type: aws.String("forward"),
},
},
})

if err != nil {
return nil, errors.Wrapf(err, "failed to create listener")
}

// Do not set existingListener
}

case *Deployment_ListenerArn:
lo, err := elbsrv.DescribeListeners(&elbv2.DescribeListenersInput{
ListenerArns: []*string{&lbRef.ListenerArn},
})
if err != nil {
return nil, errors.Wrapf(err, "failed to describe listener %q", lbRef.ListenerArn)
}
if len(lo.Listeners) == 0 {
return nil, errors.Errorf("listener %q not found", lbRef.ListenerArn)
}
existingListener = lo.Listeners[0]
lbArn = *existingListener.LoadBalancerArn

if hostname == "" {
// We need to get the hostname from the existing alb

dlb, err := elbsrv.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
LoadBalancerArns: []*string{existingListener.LoadBalancerArn},
})
if err != nil {
return nil, errors.Wrapf(err, "failed to describe load balancer for listener %q", *existingListener.LoadBalancerArn)
}

if len(dlb.LoadBalancers) == 0 {
return nil, fmt.Errorf("no load balancers returned by DescribeLoadBalancers")
}

hostname = *dlb.LoadBalancers[0].DNSName
}

}

if existingListener != nil {
log.Debug("configuring weight 100 for target group", "arn", target.TargetGroupArn)

def := existingListener.DefaultActions

if len(def) > 0 && def[0].ForwardConfig != nil {
for _, tg := range def[0].ForwardConfig.TargetGroups {
Expand All @@ -104,9 +183,9 @@ func (r *Releaser) Release(

log.Debug("modifying load balancer", "tgs", len(tgs))
_, err = elbsrv.ModifyListener(&elbv2.ModifyListenerInput{
ListenerArn: listener.ListenerArn,
Port: listener.Port,
Protocol: listener.Protocol,
ListenerArn: existingListener.ListenerArn,
Port: existingListener.Port,
Protocol: existingListener.Protocol,
DefaultActions: []*elbv2.Action{
{
ForwardConfig: &elbv2.ForwardActionConfig{
Expand All @@ -117,41 +196,13 @@ func (r *Releaser) Release(
},
})
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to modify listener %q to introduce new target group", existingListener.ListenerArn)
}
} else {
log.Info("load-balancer defined", "dns-name", *lb.DNSName)

lo, err := elbsrv.CreateListener(&elbv2.CreateListenerInput{
LoadBalancerArn: lb.LoadBalancerArn,
Port: aws.Int64(80),
Protocol: aws.String("HTTP"),
DefaultActions: []*elbv2.Action{
{
ForwardConfig: &elbv2.ForwardActionConfig{
TargetGroups: tgs,
},
Type: aws.String("forward"),
},
},
})

if err != nil {
return nil, err
}

listener = lo.Listeners[0]
}

hostname := *lb.DNSName

if r.p.config.ALB != nil && r.p.config.ALB.FQDN != "" {
hostname = r.p.config.ALB.FQDN
}

return &Release{
Url: "http://" + hostname,
LoadBalancerArn: *lb.LoadBalancerArn,
LoadBalancerArn: lbArn,
}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions embedJson/gen/platform-aws-ecs.json
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@
"SubFields": null
},
{
"Field": "load_balancer_arn",
"Type": "string",
"Field": "lb_reference",
"Type": "ecs.isDeployment_LbReference",
"Synopsis": "",
"Summary": "",
"Optional": false,
Expand Down
4 changes: 2 additions & 2 deletions website/content/partials/components/platform-aws-ecs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -387,9 +387,9 @@ Output attributes can be used in your `waypoint.hcl` as [variables](/waypoint/do

- Type: **string**

#### load_balancer_arn
#### lb_reference

- Type: **string**
- Type: **ecs.isDeployment_LbReference**

#### resource_state

Expand Down