Skip to content

Commit f974531

Browse files
authored
feat(route53): Convenience API for creating zone delegations (#1853)
When creating delegation relationship between two `PublicHostedZone`s, one can now use `zone.addDelegation(otherZone)` instead of manually creating the `ZoneDelegationRecord` insteance. This reduces the risk of passing the incorrect name server, or hosting the record on the wrong end of the relationship (DNS is hard!) Additionally, fixes a bug in which it was not possible to create a zone delegation record using a `IHostedZone.hostedZoneNameServers` property as the array was mapped, which caused the `Fn::GetAtt` stringified list token to become corrupted. Fixes #1847
1 parent 58025c0 commit f974531

File tree

5 files changed

+94
-21
lines changed

5 files changed

+94
-21
lines changed

packages/@aws-cdk/aws-route53/lib/hosted-zone.ts

+36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import ec2 = require('@aws-cdk/aws-ec2');
22
import cdk = require('@aws-cdk/cdk');
33
import { HostedZoneImportProps, IHostedZone } from './hosted-zone-ref';
4+
import { ZoneDelegationRecord } from './records';
45
import { CfnHostedZone } from './route53.generated';
56
import { validateZoneName } from './util';
67

@@ -112,6 +113,41 @@ export class PublicHostedZone extends HostedZone {
112113
public addVpc(_vpc: ec2.IVpcNetwork) {
113114
throw new Error('Cannot associate public hosted zones with a VPC');
114115
}
116+
117+
/**
118+
* Adds a delegation from this zone to a designated zone.
119+
*
120+
* @param delegate the zone being delegated to.
121+
* @param opts options for creating the DNS record, if any.
122+
*/
123+
public addDelegation(delegate: PublicHostedZone, opts: ZoneDelegationOptions = {}): void {
124+
new ZoneDelegationRecord(this, `${this.zoneName} -> ${delegate.zoneName}`, {
125+
zone: this,
126+
delegatedZoneName: delegate.zoneName,
127+
nameServers: delegate.hostedZoneNameServers!, // PublicHostedZones always have name servers!
128+
comment: opts.comment,
129+
ttl: opts.ttl,
130+
});
131+
}
132+
}
133+
134+
/**
135+
* Options available when creating a delegation relationship from one PublicHostedZone to another.
136+
*/
137+
export interface ZoneDelegationOptions {
138+
/**
139+
* A comment to add on the DNS record created to incorporate the delegation.
140+
*
141+
* @default none
142+
*/
143+
comment?: string;
144+
145+
/**
146+
* The TTL (Time To Live) of the DNS delegation record in DNS caches.
147+
*
148+
* @default 172800
149+
*/
150+
ttl?: number;
115151
}
116152

117153
export interface PrivateHostedZoneProps extends CommonHostedZoneProps {
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Construct } from '@aws-cdk/cdk';
1+
import cdk = require('@aws-cdk/cdk');
2+
import { ZoneDelegationOptions } from '../hosted-zone';
23
import { IHostedZone } from '../hosted-zone-ref';
34
import { CfnRecordSet } from '../route53.generated';
45
import { determineFullyQualifiedDomainName } from './_util';
56

6-
export interface ZoneDelegationRecordProps {
7+
export interface ZoneDelegationRecordProps extends ZoneDelegationOptions {
78
/**
89
* The zone in which this delegate is defined.
910
*/
@@ -17,38 +18,27 @@ export interface ZoneDelegationRecordProps {
1718
* The name servers to report in the delegation records.
1819
*/
1920
nameServers: string[];
20-
21-
/**
22-
* The TTL of the zone delegation records.
23-
*
24-
* @default 172800 seconds.
25-
*/
26-
ttl?: number;
27-
28-
/**
29-
* Any comments that you want to include about the zone delegation records.
30-
*
31-
* @default no comment.
32-
*/
33-
comment?: string;
3421
}
3522

3623
/**
3724
* A record to delegate further lookups to a different set of name servers
3825
*/
39-
export class ZoneDelegationRecord extends Construct {
40-
constructor(scope: Construct, id: string, props: ZoneDelegationRecordProps) {
26+
export class ZoneDelegationRecord extends cdk.Construct {
27+
constructor(scope: cdk.Construct, id: string, props: ZoneDelegationRecordProps) {
4128
super(scope, id);
4229

4330
const ttl = props.ttl === undefined ? 172_800 : props.ttl;
31+
const resourceRecords = cdk.unresolved(props.nameServers)
32+
? props.nameServers // Can't map a string-array token!
33+
: props.nameServers.map(ns => (cdk.unresolved(ns) || ns.endsWith('.')) ? ns : `${ns}.`);
4434

4535
new CfnRecordSet(this, 'Resource', {
4636
hostedZoneId: props.zone.hostedZoneId,
4737
name: determineFullyQualifiedDomainName(props.delegatedZoneName, props.zone),
4838
type: 'NS',
4939
ttl: ttl.toString(),
5040
comment: props.comment,
51-
resourceRecords: props.nameServers.map(ns => ns.endsWith('.') ? ns : `${ns}.`)
41+
resourceRecords,
5242
});
5343
}
5444
}

packages/@aws-cdk/aws-route53/test/integ.route53.expected.json

+24-1
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,29 @@
532532
"Name": "cdk.test."
533533
}
534534
},
535+
"PublicZonecdktestsubcdktest83558650": {
536+
"Type": "AWS::Route53::RecordSet",
537+
"Properties": {
538+
"Name": "sub.cdk.test.",
539+
"Type": "NS",
540+
"HostedZoneId": {
541+
"Ref": "PublicZone2E1C4E34"
542+
},
543+
"ResourceRecords": {
544+
"Fn::GetAtt": [
545+
"PublicSubZoneDBD26A0A",
546+
"NameServers"
547+
]
548+
},
549+
"TTL": "172800"
550+
}
551+
},
552+
"PublicSubZoneDBD26A0A": {
553+
"Type": "AWS::Route53::HostedZone",
554+
"Properties": {
555+
"Name": "sub.cdk.test."
556+
}
557+
},
535558
"CNAMEC70A2D52": {
536559
"Type": "AWS::Route53::RecordSet",
537560
"Properties": {
@@ -559,4 +582,4 @@
559582
}
560583
}
561584
}
562-
}
585+
}

packages/@aws-cdk/aws-route53/test/integ.route53.ts

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const privateZone = new PrivateHostedZone(stack, 'PrivateZone', {
1515
const publicZone = new PublicHostedZone(stack, 'PublicZone', {
1616
zoneName: 'cdk.test'
1717
});
18+
const publicSubZone = new PublicHostedZone(stack, 'PublicSubZone', {
19+
zoneName: 'sub.cdk.test'
20+
});
21+
publicZone.addDelegation(publicSubZone);
1822

1923
new TxtRecord(privateZone, 'TXT', {
2024
zone: privateZone,

packages/@aws-cdk/aws-route53/test/test.route53.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,27 @@ export = {
192192
// THEN
193193
test.throws(() => zone.addVpc(vpc), /Cannot associate public hosted zones with a VPC/);
194194
test.done();
195-
}
195+
},
196+
197+
'setting up zone delegation'(test: Test) {
198+
// GIVEN
199+
const stack = new cdk.Stack();
200+
const zone = new PublicHostedZone(stack, 'TopZone', { zoneName: 'top.test' });
201+
const delegate = new PublicHostedZone(stack, 'SubZone', { zoneName: 'sub.top.test' });
202+
203+
// WHEN
204+
zone.addDelegation(delegate, { ttl: 1337 });
205+
206+
// THEN
207+
expect(stack).to(haveResource('AWS::Route53::RecordSet', {
208+
Type: 'NS',
209+
Name: 'sub.top.test.',
210+
HostedZoneId: zone.node.resolve(zone.hostedZoneId),
211+
ResourceRecords: zone.node.resolve(delegate.hostedZoneNameServers),
212+
TTL: '1337',
213+
}));
214+
test.done();
215+
},
196216
};
197217

198218
class TestApp {

0 commit comments

Comments
 (0)