Skip to content

Commit 0c688dd

Browse files
authored
fix(aws-elbv2): fix load balancer registration (#890)
This change includes the following fixes that came out while using this library for ECS: - Fixes a misconception of what TargetType was supposed to represent. Directly-registered/self-registering targets and registered-by-IP /by-instance-ID are two orthogonal axes that both need configuring. Even for self-registering targets, the TargetGroup must have been set up with the correct TargetType beforehand. - The 'open' parameter said it defaulted to 'true' but it actually defaulted to 'false'. Make it actually default to 'true' now. - Make it possible for Targets to depend on the Listener via the TargetGroup, even if the TargetGroup only gets attached to the Listener later on. This is necessary for targets that can only self-attach to a TargetGroup AFTER the TargetGroup has been attached to a LoadBalancer (via the Listener). Adding a CloudFormation dependency on the Listener makes everything deploy in the right order.
1 parent fe01055 commit 0c688dd

File tree

15 files changed

+146
-38
lines changed

15 files changed

+146
-38
lines changed

packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,15 @@ export class AutoScalingGroup extends cdk.Construct implements cdk.ITaggable, el
276276
public attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps {
277277
this.targetGroupArns.push(targetGroup.targetGroupArn);
278278
targetGroup.registerConnectable(this);
279-
return { targetType: elbv2.TargetType.SelfRegistering };
279+
return { targetType: elbv2.TargetType.Instance };
280280
}
281281

282282
/**
283283
* Attach to ELBv2 Application Target Group
284284
*/
285285
public attachToNetworkTargetGroup(targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps {
286286
this.targetGroupArns.push(targetGroup.targetGroupArn);
287-
return { targetType: elbv2.TargetType.SelfRegistering };
287+
return { targetType: elbv2.TargetType.Instance };
288288
}
289289

290290
/**

packages/@aws-cdk/aws-autoscaling/test/integ.asg-w-elbv2.expected.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@
470470
"SecurityGroupIngress": [
471471
{
472472
"CidrIp": "0.0.0.0/0",
473-
"Description": "Open to the world",
473+
"Description": "Allow from anyone on port 80",
474474
"FromPort": 80,
475475
"IpProtocol": "tcp",
476476
"ToPort": 80
@@ -526,6 +526,7 @@
526526
"Properties": {
527527
"Port": 80,
528528
"Protocol": "HTTP",
529+
"TargetType": "instance",
529530
"VpcId": {
530531
"Ref": "VPCB9E5F0B4"
531532
},
@@ -534,4 +535,4 @@
534535
}
535536
}
536537
}
537-
}
538+
}

packages/@aws-cdk/aws-elasticloadbalancingv2/README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,20 +179,27 @@ load balancing target:
179179
public attachToApplicationTargetGroup(targetGroup: ApplicationTargetGroup): LoadBalancerTargetProps {
180180
targetGroup.registerConnectable(...);
181181
return {
182-
targetType: TargetType.Instance | TargetType.Ip | TargetType.SelfRegistering,
182+
targetType: TargetType.Instance | TargetType.Ip
183183
targetJson: { id: ..., port: ... },
184184
};
185185
}
186186
```
187-
188-
`targetType` should be one of `Instance` or `Ip` if the target can be directly
189-
added to the target group, or `SelfRegistering` if the target will register new
190-
instances with the load balancer at some later point.
191-
192-
If the `targetType` is `Instance` or `Ip`, `targetJson` should contain the `id`
193-
of the target (either instance ID or IP address depending on the type) and
187+
`targetType` should be one of `Instance` or `Ip`. If the target can be
188+
directly added to the target group, `targetJson` should contain the `id` of
189+
the target (either instance ID or IP address depending on the type) and
194190
optionally a `port` or `availabilityZone` override.
195191

196192
Application load balancer targets can call `registerConnectable()` on the
197193
target group to register themselves for addition to the load balancer's security
198194
group rules.
195+
196+
If your load balancer target requires that the TargetGroup has been
197+
associated with a LoadBalancer before registration can happen (such as is the
198+
case for ECS Services for example), take a resource dependency on
199+
`targetGroup.listenerDependency()` as follows:
200+
201+
```ts
202+
// Make sure that the listener has been created, and so the TargetGroup
203+
// has been associated with the LoadBalancer, before 'resource' is created.
204+
resourced.addDependency(targetGroup.listenerDependency());
205+
```

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-listener.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
134134

135135
(props.defaultTargetGroups || []).forEach(this.addDefaultTargetGroup.bind(this));
136136

137-
if (props.open) {
137+
if (props.open !== false) {
138138
this.connections.allowDefaultPortFrom(new ec2.AnyIPv4(), `Allow from anyone on port ${port}`);
139139
}
140140
}
@@ -258,7 +258,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
258258
/**
259259
* Properties to reference an existing listener
260260
*/
261-
export interface IApplicationListener extends ec2.IConnectable {
261+
export interface IApplicationListener extends ec2.IConnectable, cdk.IDependable {
262262
/**
263263
* ARN of the listener
264264
*/
@@ -319,6 +319,7 @@ export interface ApplicationListenerRefProps {
319319
}
320320

321321
class ImportedApplicationListener extends cdk.Construct implements IApplicationListener {
322+
public readonly dependencyElements: cdk.IDependable[] = [];
322323
public readonly connections: ec2.Connections;
323324

324325
/**

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/alb/application-target-group.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import cdk = require('@aws-cdk/cdk');
33
import { BaseTargetGroup, BaseTargetGroupProps, ITargetGroup, LoadBalancerTargetProps, TargetGroupRefProps } from '../shared/base-target-group';
44
import { ApplicationProtocol } from '../shared/enums';
55
import { BaseImportedTargetGroup } from '../shared/imported';
6-
import { determineProtocolAndPort } from '../shared/util';
6+
import { determineProtocolAndPort, LazyDependable } from '../shared/util';
77
import { IApplicationListener } from './application-listener';
88

99
/**
@@ -144,6 +144,7 @@ export class ApplicationTargetGroup extends BaseTargetGroup {
144144
listener.registerConnectable(member.connectable, member.portRange);
145145
}
146146
this.listeners.push(listener);
147+
this.dependableListeners.push(listener);
147148
}
148149
}
149150

@@ -181,6 +182,10 @@ class ImportedApplicationTargetGroup extends BaseImportedTargetGroup implements
181182
public registerListener(_listener: IApplicationListener) {
182183
// Nothing to do, we know nothing of our members
183184
}
185+
186+
public listenerDependency(): cdk.IDependable {
187+
return new LazyDependable([]);
188+
}
184189
}
185190

186191
/**

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-listener.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export class NetworkListener extends BaseListener implements INetworkListener {
113113
/**
114114
* Properties to reference an existing listener
115115
*/
116-
export interface INetworkListener {
116+
export interface INetworkListener extends cdk.IDependable {
117117
/**
118118
* ARN of the listener
119119
*/
@@ -134,6 +134,8 @@ export interface NetworkListenerRefProps {
134134
* An imported Network Listener
135135
*/
136136
class ImportedNetworkListener extends cdk.Construct implements INetworkListener {
137+
public readonly dependencyElements: cdk.IDependable[] = [];
138+
137139
/**
138140
* ARN of the listener
139141
*/

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/nlb/network-target-group.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import cdk = require('@aws-cdk/cdk');
22
import { BaseTargetGroup, BaseTargetGroupProps, ITargetGroup, LoadBalancerTargetProps, TargetGroupRefProps } from '../shared/base-target-group';
33
import { Protocol } from '../shared/enums';
44
import { BaseImportedTargetGroup } from '../shared/imported';
5+
import { LazyDependable } from '../shared/util';
6+
import { INetworkListener } from './network-listener';
57

68
/**
79
* Properties for a new Network Target Group
@@ -62,6 +64,15 @@ export class NetworkTargetGroup extends BaseTargetGroup {
6264
this.addLoadBalancerTarget(result);
6365
}
6466
}
67+
68+
/**
69+
* Register a listener that is load balancing to this target group.
70+
*
71+
* Don't call this directly. It will be called by listeners.
72+
*/
73+
public registerListener(listener: INetworkListener) {
74+
this.dependableListeners.push(listener);
75+
}
6576
}
6677

6778
/**
@@ -75,6 +86,9 @@ export interface INetworkTargetGroup extends ITargetGroup {
7586
* An imported network target group
7687
*/
7788
class ImportedNetworkTargetGroup extends BaseImportedTargetGroup implements INetworkTargetGroup {
89+
public listenerDependency(): cdk.IDependable {
90+
return new LazyDependable([]);
91+
}
7892
}
7993

8094
/**

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-listener.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { ITargetGroup } from './base-target-group';
55
/**
66
* Base class for listeners
77
*/
8-
export abstract class BaseListener extends cdk.Construct {
8+
export abstract class BaseListener extends cdk.Construct implements cdk.IDependable {
9+
public readonly dependencyElements: cdk.IDependable[];
910
public readonly listenerArn: string;
1011
private readonly defaultActions: any[] = [];
1112

@@ -17,6 +18,7 @@ export abstract class BaseListener extends cdk.Construct {
1718
defaultActions: new cdk.Token(() => this.defaultActions),
1819
});
1920

21+
this.dependencyElements = [resource];
2022
this.listenerArn = resource.ref;
2123
}
2224

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/base-target-group.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import ec2 = require('@aws-cdk/aws-ec2');
33
import cdk = require('@aws-cdk/cdk');
44
import { cloudformation } from '../elasticloadbalancingv2.generated';
55
import { Protocol, TargetType } from './enums';
6-
import { Attributes, renderAttributes } from './util';
6+
import { Attributes, LazyDependable, renderAttributes } from './util';
77

88
/**
99
* Basic properties of both Application and Network Target Groups
@@ -146,6 +146,11 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr
146146
*/
147147
protected readonly defaultPort: string;
148148

149+
/**
150+
* List of listeners routing to this target group
151+
*/
152+
protected readonly dependableListeners = new Array<cdk.IDependable>();
153+
149154
/**
150155
* Attributes of this target group
151156
*/
@@ -242,19 +247,21 @@ export abstract class BaseTargetGroup extends cdk.Construct implements ITargetGr
242247
};
243248
}
244249

250+
/**
251+
* Return an object to depend on the listeners added to this target group
252+
*/
253+
public listenerDependency(): cdk.IDependable {
254+
return new LazyDependable(this.dependableListeners);
255+
}
256+
245257
/**
246258
* Register the given load balancing target as part of this group
247259
*/
248260
protected addLoadBalancerTarget(props: LoadBalancerTargetProps) {
249-
if ((props.targetType === TargetType.SelfRegistering) !== (props.targetJson === undefined)) {
250-
throw new Error('Load balancing target should specify targetJson if and only if TargetType is not SelfRegistering');
251-
}
252-
if (props.targetType !== TargetType.SelfRegistering) {
253-
if (this.targetType !== undefined && this.targetType !== props.targetType) {
254-
throw new Error(`Already have a of type '${this.targetType}', adding '${props.targetType}'; make all targets the same type.`);
255-
}
256-
this.targetType = props.targetType;
261+
if (this.targetType !== undefined && this.targetType !== props.targetType) {
262+
throw new Error(`Already have a of type '${this.targetType}', adding '${props.targetType}'; make all targets the same type.`);
257263
}
264+
this.targetType = props.targetType;
258265

259266
if (props.targetJson) {
260267
this.targetsJson.push(props.targetJson);
@@ -285,6 +292,11 @@ export interface ITargetGroup {
285292
* ARN of the target group
286293
*/
287294
readonly targetGroupArn: string;
295+
296+
/**
297+
* Return an object to depend on the listeners added to this target group
298+
*/
299+
listenerDependency(): cdk.IDependable;
288300
}
289301

290302
/**
@@ -298,6 +310,8 @@ export interface LoadBalancerTargetProps {
298310

299311
/**
300312
* JSON representing the target's direct addition to the TargetGroup list
313+
*
314+
* May be omitted if the target is going to register itself later.
301315
*/
302316
targetJson?: any;
303317
}

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/enums.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,4 @@ export enum TargetType {
109109
* Targets identified by IP address
110110
*/
111111
Ip = 'ip',
112-
113-
/**
114-
* A target that will register itself with the target group
115-
*/
116-
SelfRegistering = 'self-registering',
117112
}

packages/@aws-cdk/aws-elasticloadbalancingv2/lib/shared/util.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cdk = require('@aws-cdk/cdk');
12
import { ApplicationProtocol } from "./enums";
23

34
export type Attributes = {[key: string]: string | undefined};
@@ -67,3 +68,15 @@ export function determineProtocolAndPort(protocol: ApplicationProtocol | undefin
6768
export function ifUndefined<T>(x: T | undefined, def: T) {
6869
return x !== undefined ? x : def;
6970
}
71+
72+
/**
73+
* Allow lazy evaluation of a list of dependables
74+
*/
75+
export class LazyDependable implements cdk.IDependable {
76+
constructor(private readonly depList: cdk.IDependable[]) {
77+
}
78+
79+
public get dependencyElements(): cdk.IDependable[] {
80+
return this.depList;
81+
}
82+
}

packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.listener.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, haveResource } from '@aws-cdk/assert';
1+
import { expect, haveResource, MatchStyle } from '@aws-cdk/assert';
22
import ec2 = require('@aws-cdk/aws-ec2');
33
import cdk = require('@aws-cdk/cdk');
44
import { Test } from 'nodeunit';
@@ -47,6 +47,33 @@ export = {
4747
test.done();
4848
},
4949

50+
'Listener default to open'(test: Test) {
51+
// GIVEN
52+
const stack = new cdk.Stack();
53+
const vpc = new ec2.VpcNetwork(stack, 'Stack');
54+
const loadBalancer = new elbv2.ApplicationLoadBalancer(stack, 'LB', { vpc });
55+
56+
// WHEN
57+
loadBalancer.addListener('MyListener', {
58+
port: 80,
59+
defaultTargetGroups: [new elbv2.ApplicationTargetGroup(stack, 'Group', { vpc, port: 80 })]
60+
});
61+
62+
// THEN
63+
expect(stack).to(haveResource('AWS::EC2::SecurityGroup', {
64+
SecurityGroupIngress: [
65+
{
66+
CidrIp: "0.0.0.0/0",
67+
FromPort: 80,
68+
IpProtocol: "tcp",
69+
ToPort: 80
70+
}
71+
]
72+
}));
73+
74+
test.done();
75+
},
76+
5077
'HTTPS listener requires certificate'(test: Test) {
5178
// GIVEN
5279
const stack = new cdk.Stack();
@@ -306,4 +333,33 @@ export = {
306333

307334
test.done();
308335
},
336+
337+
'Can depend on eventual listener via TargetGroup'(test: Test) {
338+
// GIVEN
339+
const stack = new cdk.Stack();
340+
const vpc = new ec2.VpcNetwork(stack, 'VPC');
341+
const loadBalancer = new elbv2.ApplicationLoadBalancer(stack, 'LoadBalancer', { vpc });
342+
const group = new elbv2.ApplicationTargetGroup(stack, 'TargetGroup', { vpc, port: 80 });
343+
344+
// WHEN
345+
const resource = new cdk.Resource(stack, 'SomeResource', { type: 'Test::Resource' });
346+
resource.addDependency(group.listenerDependency());
347+
348+
loadBalancer.addListener('Listener', {
349+
port: 80,
350+
defaultTargetGroups: [group]
351+
});
352+
353+
// THEN
354+
expect(stack).toMatch({
355+
Resources: {
356+
SomeResource: {
357+
Type: "Test::Resource",
358+
DependsOn: ["LoadBalancerListenerE1A099B9"]
359+
}
360+
}
361+
}, MatchStyle.SUPERSET);
362+
363+
test.done();
364+
}
309365
};

packages/@aws-cdk/aws-elasticloadbalancingv2/test/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ export class FakeSelfRegisteringTarget extends cdk.Construct implements elbv2.IA
1717

1818
public attachToApplicationTargetGroup(targetGroup: elbv2.ApplicationTargetGroup): elbv2.LoadBalancerTargetProps {
1919
targetGroup.registerConnectable(this);
20-
return { targetType: elbv2.TargetType.SelfRegistering };
20+
return { targetType: elbv2.TargetType.Instance };
2121
}
2222

2323
public attachToNetworkTargetGroup(_targetGroup: elbv2.NetworkTargetGroup): elbv2.LoadBalancerTargetProps {
24-
return { targetType: elbv2.TargetType.SelfRegistering };
24+
return { targetType: elbv2.TargetType.Instance };
2525
}
2626
}

0 commit comments

Comments
 (0)