Skip to content

Commit

Permalink
feat(ecs-patterns): add option to create cname instead of alias record (
Browse files Browse the repository at this point in the history
#10812)

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
hoegertn authored Nov 6, 2020
1 parent 9968669 commit 89a5055
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 14 deletions.
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-ecs-patterns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Fargate services use the default VPC Security Group unless one or more are provi

By setting `redirectHTTP` to true, CDK will automatically create a listener on port 80 that redirects HTTP traffic to the HTTPS port.

If you specify the option `recordType` you can decide if you want the construct to use CNAME or Route53-Aliases as record sets.

Additionally, if more than one application target group are needed, instantiate one of the following:

* `ApplicationMultipleTargetGroupsEc2Service`
Expand Down Expand Up @@ -153,6 +155,8 @@ The CDK will create a new Amazon ECS cluster if you specify a VPC and omit `clus

If `cluster` and `vpc` are omitted, the CDK creates a new VPC with subnets in two Availability Zones and a cluster within this VPC.

If you specify the option `recordType` you can decide if you want the construct to use CNAME or Route53-Aliases as record sets.

Additionally, if more than one network target group is needed, instantiate one of the following:

* NetworkMultipleTargetGroupsEc2Service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,29 @@ import {
IApplicationLoadBalancer, ListenerCertificate, ListenerAction,
} from '@aws-cdk/aws-elasticloadbalancingv2';
import { IRole } from '@aws-cdk/aws-iam';
import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { ARecord, IHostedZone, RecordTarget, CnameRecord } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Describes the type of DNS record the service should create
*/
export enum ApplicationLoadBalancedServiceRecordType {
/**
* Create Route53 A Alias record
*/
ALIAS,
/**
* Create a CNAME record
*/
CNAME,
/**
* Do not create any DNS records
*/
NONE
}

/**
* The properties for the base ApplicationLoadBalancedEc2Service or ApplicationLoadBalancedFargateService service.
*/
Expand Down Expand Up @@ -177,6 +195,14 @@ export interface ApplicationLoadBalancedServiceBaseProps {
* @default false
*/
readonly redirectHTTP?: boolean;

/**
* Specifies whether the Route53 record should be a CNAME, an A record using the Alias feature or no record at all.
* This is useful if you need to work with DNS systems that do not support alias records.
*
* @default ApplicationLoadBalancedServiceRecordType.ALIAS
*/
readonly recordType?: ApplicationLoadBalancedServiceRecordType;
}

export interface ApplicationLoadBalancedTaskImageOptions {
Expand Down Expand Up @@ -390,13 +416,27 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct {
throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
}

const record = new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});

domainName = record.domainName;
switch (props.recordType ?? ApplicationLoadBalancedServiceRecordType.ALIAS) {
case ApplicationLoadBalancedServiceRecordType.ALIAS:
let aliasRecord = new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
domainName = aliasRecord.domainName;
break;
case ApplicationLoadBalancedServiceRecordType.CNAME:
let cnameRecord = new CnameRecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
domainName: loadBalancer.loadBalancerDnsName,
});
domainName = cnameRecord.domainName;
break;
case ApplicationLoadBalancedServiceRecordType.NONE:
// Do not create a DNS record
break;
}
}

if (loadBalancer instanceof ApplicationLoadBalancer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@ import { IVpc } from '@aws-cdk/aws-ec2';
import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs';
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 { ARecord, CnameRecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53';
import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets';
import * as cdk from '@aws-cdk/core';
import { Construct } from 'constructs';

/**
* Describes the type of DNS record the service should create
*/
export enum NetworkLoadBalancedServiceRecordType {
/**
* Create Route53 A Alias record
*/
ALIAS,
/**
* Create a CNAME record
*/
CNAME,
/**
* Do not create any DNS records
*/
NONE
}

/**
* The properties for the base NetworkLoadBalancedEc2Service or NetworkLoadBalancedFargateService service.
*/
Expand Down Expand Up @@ -136,6 +154,14 @@ export interface NetworkLoadBalancedServiceBaseProps {
* @default - AWS Cloud Map service discovery is not enabled.
*/
readonly cloudMapOptions?: CloudMapOptions;

/**
* Specifies whether the Route53 record should be a CNAME, an A record using the Alias feature or no record at all.
* This is useful if you need to work with DNS systems that do not support alias records.
*
* @default NetworkLoadBalancedServiceRecordType.ALIAS
*/
readonly recordType?: NetworkLoadBalancedServiceRecordType;
}

export interface NetworkLoadBalancedTaskImageOptions {
Expand Down Expand Up @@ -294,11 +320,25 @@ export abstract class NetworkLoadBalancedServiceBase extends cdk.Construct {
throw new Error('A Route53 hosted domain zone name is required to configure the specified domain name');
}

new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
switch (props.recordType ?? NetworkLoadBalancedServiceRecordType.ALIAS) {
case NetworkLoadBalancedServiceRecordType.ALIAS:
new ARecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
target: RecordTarget.fromAlias(new LoadBalancerTarget(loadBalancer)),
});
break;
case NetworkLoadBalancedServiceRecordType.CNAME:
new CnameRecord(this, 'DNS', {
zone: props.domainZone,
recordName: props.domainName,
domainName: loadBalancer.loadBalancerDnsName,
});
break;
case NetworkLoadBalancedServiceRecordType.NONE:
// Do not create a DNS record
break;
}
}

if (loadBalancer instanceof NetworkLoadBalancer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,123 @@ export = {
test.done();
},

'setting ALB cname option correctly sets the recordset type'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', {
cluster,
protocol: ApplicationProtocol.HTTPS,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.CNAME,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::Route53::RecordSet', {
Name: 'test.domain.com.',
Type: 'CNAME',
}));

test.done();
},

'setting ALB record type to NONE correctly omits the recordset'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'FargateAlbService', {
cluster,
protocol: ApplicationProtocol.HTTPS,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.ApplicationLoadBalancedServiceRecordType.NONE,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).notTo(haveResource('AWS::Route53::RecordSet'));

test.done();
},


'setting NLB cname option correctly sets the recordset type'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', {
cluster,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.CNAME,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).to(haveResourceLike('AWS::Route53::RecordSet', {
Name: 'test.domain.com.',
Type: 'CNAME',
}));

test.done();
},

'setting NLB record type to NONE correctly omits the recordset'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'VPC');
const cluster = new ecs.Cluster(stack, 'Cluster', { vpc });

// WHEN
new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'FargateNlbService', {
cluster,
domainName: 'test.domain.com',
domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', {
hostedZoneId: 'fakeId',
zoneName: 'domain.com.',
}),
recordType: ecsPatterns.NetworkLoadBalancedServiceRecordType.NONE,
taskImageOptions: {
containerPort: 2015,
image: ecs.ContainerImage.fromRegistry('abiosoft/caddy'),
},
});

// THEN
expect(stack).notTo(haveResource('AWS::Route53::RecordSet'));

test.done();
},

'setting ALB HTTP protocol to create the listener on 80'(test: Test) {
// GIVEN
const stack = new cdk.Stack();
Expand Down

0 comments on commit 89a5055

Please sign in to comment.