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

feat(aws-ecs): Support HTTPS in load balanced Fargate service #1115

Merged
merged 7 commits into from
Nov 14, 2018
Merged
41 changes: 40 additions & 1 deletion packages/@aws-cdk/aws-ecs/lib/load-balanced-fargate-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { CertificateRef } from '@aws-cdk/aws-certificatemanager';
import elbv2 = require('@aws-cdk/aws-elasticloadbalancingv2');
import { AliasRecord, HostedZoneRef } from '@aws-cdk/aws-route53';
import cdk = require('@aws-cdk/cdk');
import { ICluster } from './cluster';
import { IContainerImage } from './container-image';
Expand Down Expand Up @@ -83,6 +85,22 @@ export interface LoadBalancedFargateServiceProps {
* @default 1
*/
desiredCount?: number;

/*
* Domain name for the service, e.g. api.example.com
*/
domainName?: string;

/**
* Route53 hosted zone for the domain, e.g. "example.com."
*/
domainZone?: HostedZoneRef;

/**
* Certificate Manager certificate to associate with the load balancer.
* Setting this option will set the load balancer port to 443.
*/
certificate?: CertificateRef;
}

/**
Expand Down Expand Up @@ -123,12 +141,33 @@ export class LoadBalancedFargateService extends cdk.Construct {

this.loadBalancer = lb;

const listener = lb.addListener('PublicListener', { port: 80, open: true });
let listener;
if (typeof props.certificate !== 'undefined') {
listener = lb.addListener('PublicListener', {
port: 443,
open: true,
certificateArns: [props.certificate.certificateArn]
});
} else {
listener = lb.addListener('PublicListener', { port: 80, open: true });
}

listener.addTargets('ECS', {
port: 80,
targets: [service]
});

new cdk.Output(this, 'LoadBalancerDNS', { value: lb.dnsName });

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');
}

new AliasRecord(props.domainZone, "DNS", {
recordName: props.domainName,
target: lb
});
}
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-ecs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"dependencies": {
"@aws-cdk/aws-applicationautoscaling": "^0.15.2",
"@aws-cdk/aws-autoscaling": "^0.15.2",
"@aws-cdk/aws-certificatemanager": "^0.15.2",
"@aws-cdk/aws-cloudformation": "^0.15.2",
"@aws-cdk/aws-cloudwatch": "^0.15.2",
"@aws-cdk/aws-ec2": "^0.15.2",
Expand All @@ -72,6 +73,7 @@
"@aws-cdk/aws-iam": "^0.15.2",
"@aws-cdk/aws-lambda": "^0.15.2",
"@aws-cdk/aws-logs": "^0.15.2",
"@aws-cdk/aws-route53": "^0.15.2",
"@aws-cdk/cdk": "^0.15.2",
"@aws-cdk/cx-api": "^0.15.2"
},
Expand Down
70 changes: 70 additions & 0 deletions packages/@aws-cdk/aws-ecs/test/test.l3s.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { CertificateRef } from '@aws-cdk/aws-certificatemanager';
import ec2 = require('@aws-cdk/aws-ec2');
import { PublicHostedZone } from '@aws-cdk/aws-route53';
import cdk = require('@aws-cdk/cdk');
import { Test } from 'nodeunit';
import ecs = require('../lib');
Expand Down Expand Up @@ -52,6 +54,74 @@ export = {
LaunchType: "FARGATE",
}));

expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', {
Port: 80
}));

test.done();
},

'test Fargateloadbalanced construct with TLS'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.VpcNetwork(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });
const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' });

// WHEN
new ecs.LoadBalancedFargateService(stack, 'Service', {
cluster,
image: ecs.DockerHub.image('test'),
domainName: 'api.example.com',
domainZone: zone,
certificate: CertificateRef.import(stack, 'Cert', { certificateArn: 'helloworld' })
});

// THEN - stack contains a load balancer and a service
expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer'));

expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', {
Port: 443,
Certificates: [{
CertificateArn: "helloworld"
}]
}));

expect(stack).to(haveResource("AWS::ECS::Service", {
DesiredCount: 1,
LaunchType: "FARGATE",
}));

expect(stack).to(haveResource('AWS::Route53::RecordSet', {
Name: 'api.example.com.',
HostedZoneId: {
Ref: "HostedZoneDB99F866"
},
Type: 'A',
AliasTarget: {
HostedZoneId: { 'Fn::GetAtt': [ 'ServiceLBE9A1ADBC', 'CanonicalHostedZoneID' ] },
DNSName: { 'Fn::GetAtt': [ 'ServiceLBE9A1ADBC', 'DNSName' ] },
}
}));

test.done();
},

"errors when setting domainName but not domainZone"(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.VpcNetwork(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// THEN
test.throws(() => {
new ecs.LoadBalancedFargateService(stack, 'Service', {
cluster,
image: ecs.DockerHub.image('test'),
domainName: 'api.example.com'
});
});

test.done();
},

Expand Down