Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ecs-patterns): allow imported load balancers as inputs #5461

Merged
merged 19 commits into from
Feb 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2e576f4
feat(ecs-patterns): allow imported load balancers in alb/nlb constructs
bvtujo Dec 11, 2019
1aeea91
fix failing tests
bvtujo Dec 20, 2019
08dcda3
Check load balancer resources in unit tests involving previously crea…
bvtujo Dec 20, 2019
30b6c16
Merge branch 'master' into bvtujo/allow-imported-lb
piradeepk Dec 20, 2019
6eee70b
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Jan 2, 2020
8bb4e6a
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Jan 28, 2020
16d8764
Address feedback on comments re: docstrings
bvtujo Jan 28, 2020
586b8ca
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Jan 30, 2020
6be572a
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Jan 31, 2020
1bd5799
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Feb 4, 2020
8c6fec5
Address feedback in comments & resolve typo
bvtujo Feb 5, 2020
0b8ce4a
Merge branch 'master' into bvtujo/allow-imported-lb
piradeepk Feb 5, 2020
6185268
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Feb 5, 2020
0e4c0b1
Merge branch 'master' into bvtujo/allow-imported-lb
bvtujo Feb 5, 2020
3a0b3bc
Merge branch 'master' into bvtujo/allow-imported-lb
piradeepk Feb 7, 2020
9ba9d5c
Merge branch 'master' of github.com:aws/aws-cdk into bvtujo/allow-imp…
bvtujo Feb 12, 2020
57cd88a
Refactor .loadBalancer properties to use getter pattern
bvtujo Feb 12, 2020
acedf99
Merge branch 'bvtujo/allow-imported-lb' of github.com:bvtujo/aws-cdk …
bvtujo Feb 12, 2020
cc61cb0
Merge branch 'master' into bvtujo/allow-imported-lb
piradeepk Feb 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { DnsValidatedCertificate, ICertificate } from '@aws-cdk/aws-certificatemanager';
import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, ListenerCertificate } from '@aws-cdk/aws-elasticloadbalancingv2';
import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup,
IApplicationLoadBalancer, ListenerCertificate} from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
Expand Down Expand Up @@ -119,12 +120,14 @@ export interface ApplicationLoadBalancedServiceBaseProps {

/**
* The application load balancer that will serve traffic to the service.
* The VPC attribute of a load balancer must be specified for it to be used
* to create a new service with this pattern.
*
* [disable-awslint:ref-via-interface]
*
* @default - a new load balancer will be created.
*/
readonly loadBalancer?: ApplicationLoadBalancer;
readonly loadBalancer?: IApplicationLoadBalancer;

/**
* Listener port of the application load balancer that will serve traffic to the service.
Expand Down Expand Up @@ -252,7 +255,12 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
/**
* The Application Load Balancer for the service.
*/
public readonly loadBalancer: ApplicationLoadBalancer;
public get loadBalancer(): ApplicationLoadBalancer {
if (!this._applicationLoadBalancer) {
throw new Error('.loadBalancer can only be accessed if the class was constructed with an owned, not imported, load balancer');
}
return this._applicationLoadBalancer;
}

/**
* The listener for the service.
Expand All @@ -274,6 +282,8 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
*/
public readonly cluster: ICluster;

private readonly _applicationLoadBalancer?: ApplicationLoadBalancer;

/**
* Constructs a new instance of the ApplicationLoadBalancedServiceBase class.
*/
Expand All @@ -297,18 +307,20 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
internetFacing
};

this.loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer : new ApplicationLoadBalancer(this, 'LB', lbProps);
const loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer
: new ApplicationLoadBalancer(this, 'LB', lbProps);

if (props.certificate !== undefined && props.protocol !== undefined && props.protocol !== ApplicationProtocol.HTTPS) {
throw new Error('The HTTPS protocol must be used when a certificate is given');
}
const protocol = props.protocol !== undefined ? props.protocol : (props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP);
const protocol = props.protocol !== undefined ? props.protocol :
(props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP);

const targetProps = {
port: 80
};

this.listener = this.loadBalancer.addListener('PublicListener', {
this.listener = loadBalancer.addListener('PublicListener', {
protocol,
port: props.listenerPort,
open: true
Expand All @@ -333,7 +345,7 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
this.listener.addCertificates('Arns', [ListenerCertificate.fromCertificateManager(this.certificate)]);
}

let domainName = this.loadBalancer.loadBalancerDnsName;
let domainName = loadBalancer.loadBalancerDnsName;
if (typeof props.domainName !== 'undefined') {
if (typeof props.domainZone === 'undefined') {
throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
Expand All @@ -342,13 +354,17 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
const record = new ARecord(this, "DNS", {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(this.loadBalancer)),
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});

domainName = record.domainName;
}

new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName });
if (loadBalancer instanceof ApplicationLoadBalancer) {
this._applicationLoadBalancer = loadBalancer;
}

new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: loadBalancer.loadBalancerDnsName });
new cdk.CfnOutput(this, 'ServiceURL', { value: protocol.toLowerCase() + '://' + domainName });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
import { NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
import { INetworkLoadBalancer, NetworkListener, NetworkLoadBalancer, NetworkTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
Expand Down Expand Up @@ -97,12 +97,14 @@ export interface NetworkLoadBalancedServiceBaseProps {

/**
* The network load balancer that will serve traffic to the service.
* If the load balancer has been imported, the vpc attribute must be specified
* in the call to fromNetworkLoadBalancerAttributes().
*
* [disable-awslint:ref-via-interface]
*
* @default - a new load balancer will be created.
*/
readonly loadBalancer?: NetworkLoadBalancer;
readonly loadBalancer?: INetworkLoadBalancer;

/**
* Listener port of the network load balancer that will serve traffic to the service.
Expand Down Expand Up @@ -228,7 +230,12 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
/**
* The Network Load Balancer for the service.
*/
public readonly loadBalancer: NetworkLoadBalancer;
public get loadBalancer(): NetworkLoadBalancer {
if (!this._networkLoadBalancer) {
throw new Error(".loadBalancer can only be accessed if the class was constructed with an owned, not imported, load balancer");
}
return this._networkLoadBalancer;
}

/**
* The listener for the service.
Expand All @@ -245,6 +252,7 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
*/
public readonly cluster: ICluster;

private readonly _networkLoadBalancer?: NetworkLoadBalancer;
/**
* Constructs a new instance of the NetworkLoadBalancedServiceBase class.
*/
Expand All @@ -268,15 +276,16 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
internetFacing
};

this.loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer : new NetworkLoadBalancer(this, 'LB', lbProps);
const loadBalancer = props.loadBalancer !== undefined ? props.loadBalancer :
new NetworkLoadBalancer(this, 'LB', lbProps);

const listenerPort = props.listenerPort !== undefined ? props.listenerPort : 80;

const targetProps = {
port: 80
};

this.listener = this.loadBalancer.addListener('PublicListener', { port: listenerPort });
this.listener = loadBalancer.addListener('PublicListener', { port: listenerPort });
this.targetGroup = this.listener.addTargets('ECS', targetProps);

if (typeof props.domainName !== 'undefined') {
Expand All @@ -287,11 +296,17 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
new ARecord(this, "DNS", {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(this.loadBalancer)),
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
}

new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName });
if (loadBalancer instanceof NetworkLoadBalancer) {
this._networkLoadBalancer = loadBalancer;
}

if (props.loadBalancer === undefined) {
new cdk.CfnOutput(this, 'LoadBalancerDNS', { value: this.loadBalancer.loadBalancerDnsName });
}
}

/**
Expand Down
152 changes: 150 additions & 2 deletions packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert';
import { Certificate } from '@aws-cdk/aws-certificatemanager';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import { ApplicationProtocol } from '@aws-cdk/aws-elasticloadbalancingv2';
import { ApplicationLoadBalancer, ApplicationProtocol, NetworkLoadBalancer } from '@aws-cdk/aws-elasticloadbalancingv2';
import { PublicHostedZone } from '@aws-cdk/aws-route53';
import * as cloudmap from '@aws-cdk/aws-servicediscovery';
import * as cdk from '@aws-cdk/core';
Expand Down Expand Up @@ -785,7 +785,6 @@ export = {

test.done();
},

'ALBFargate - having *HealthyPercent properties'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -901,4 +900,153 @@ export = {

test.done();
},

'NetworkLoadbalancedEC2Service accepts previously created load balancer'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Vpc');
const cluster = new ecs.Cluster(stack, "Cluster", {vpc, clusterName: "MyCluster" });
cluster.addCapacity("Capacity", {instanceType: new ec2.InstanceType('t2.micro')});
const nlb = new NetworkLoadBalancer(stack, 'NLB', { vpc });
const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDef.addContainer('Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
memoryLimitMiB: 1024,
});
container.addPortMappings({ containerPort: 80 });

// WHEN
new ecsPatterns.NetworkLoadBalancedEc2Service(stack, 'Service', {
cluster,
loadBalancer: nlb,
taskDefinition: taskDef,
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
LaunchType: 'EC2'
}));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::LoadBalancer', {
Type: 'network',
}));
test.done();
},

'NetworkLoadBalancedEC2Service accepts imported load balancer'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const nlbArn = "arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer";
const vpc = new ec2.Vpc(stack, "Vpc");
const cluster = new ecs.Cluster(stack, "Cluster", {vpc, clusterName: "MyCluster" });
cluster.addCapacity("Capacity", {instanceType: new ec2.InstanceType('t2.micro')});
const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes(stack, "NLB", {
loadBalancerArn: nlbArn,
vpc,
});
const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDef.addContainer('Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
memoryLimitMiB: 1024,
});
container.addPortMappings({
containerPort: 80,
});

// WHEN
new ecsPatterns.NetworkLoadBalancedEc2Service(stack, "Service", {
cluster,
loadBalancer: nlb,
desiredCount: 1,
taskDefinition: taskDef
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
LaunchType: 'EC2',
LoadBalancers: [{ContainerName: 'Container', ContainerPort: 80}]
}));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::TargetGroup'));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', {
LoadBalancerArn: nlb.loadBalancerArn,
Port: 80,
}));
test.done();
},

'ApplicationLoadBalancedEC2Service accepts previously created load balancer'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Vpc');
const cluster = new ecs.Cluster(stack, "Cluster", {vpc, clusterName: "MyCluster" });
cluster.addCapacity("Capacity", {instanceType: new ec2.InstanceType('t2.micro')});
const sg = new ec2.SecurityGroup(stack, 'SG', { vpc });
const alb = new ApplicationLoadBalancer(stack, 'NLB', {
vpc,
securityGroup: sg,
});
const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDef.addContainer('Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
memoryLimitMiB: 1024,
});
container.addPortMappings({ containerPort: 80 });

// WHEN
new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', {
cluster,
loadBalancer: alb,
taskDefinition: taskDef,
});

// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
LaunchType: 'EC2'
}));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::LoadBalancer', {
Type: 'application'
}));
test.done();
},

'ApplicationLoadBalancedEC2Service accepts imported load balancer'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const albArn = "arn:aws:elasticloadbalancing::000000000000::dummyloadbalancer";
const vpc = new ec2.Vpc(stack, "Vpc");
const cluster = new ecs.Cluster(stack, "Cluster", {vpc, clusterName: "MyCluster" });
cluster.addCapacity("Capacity", {instanceType: new ec2.InstanceType('t2.micro')});
const sg = new ec2.SecurityGroup(stack, "SG", { vpc, });
const alb = ApplicationLoadBalancer.fromApplicationLoadBalancerAttributes(stack, 'ALB', {
loadBalancerArn: albArn,
vpc,
securityGroupId: sg.securityGroupId,
loadBalancerDnsName: "MyName"
});
const taskDef = new ecs.Ec2TaskDefinition(stack, 'TaskDef');
const container = taskDef.addContainer('Container', {
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
memoryLimitMiB: 1024,
});
container.addPortMappings({
containerPort: 80,
});
// WHEN
new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, "Service", {
cluster,
loadBalancer: alb,
taskDefinition: taskDef,
});
// THEN
expect(stack).to(haveResourceLike('AWS::ECS::Service', {
LaunchType: 'EC2',
LoadBalancers: [{ContainerName: 'Container', ContainerPort: 80}]
}));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::TargetGroup'));
expect(stack).to(haveResourceLike('AWS::ElasticLoadBalancingV2::Listener', {
LoadBalancerArn: alb.loadBalancerArn,
Port: 80,
}));

test.done();
},
};
Loading