Skip to content

Commit

Permalink
feat(route53): support CNAME records (#1487)
Browse files Browse the repository at this point in the history
Adds support for CNAME records via the `CnameRecord` construct.

Misc:

- `TXTRecord` was renamed to `TxtRecord`.
- `hostedZoneNameServers` attribute added to IHostedZone
- Made `HostedZone` a concrete (non-abstract) class so it will be
  compatible with how CloudFormation represents this resource,
  but left PublicHostedZone and PrivateHostedZone to allow
  a more strongly-typed experience if you like.

Credits for original PR (closes #1420): @MikeBild

BREAKING CHANGE: The `route53.TXTRecord` class was renamed to `route53.TxtRecord`.
  • Loading branch information
Elad Ben-Israel authored Jan 8, 2019
1 parent 471e8eb commit 17eddd1
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 117 deletions.
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-route53/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## AWS Route53 Constuct Library
## AWS Route53 Construct Library

To add a public hosted zone:

Expand Down Expand Up @@ -34,7 +34,7 @@ To add a TXT record to your zone:
```ts
import route53 = require('@aws-cdk/aws-route53');

new route53.TXTRecord(zone, 'TXTRecord', {
new route53.TxtRecord(zone, 'TXTRecord', {
recordName: '_foo', // If the name ends with a ".", it will be used as-is;
// if it ends with a "." followed by the zone name, a trailing "." will be added automatically;
// otherwise, a ".", the zone name, and a trailing "." will be added automatically.
Expand Down
10 changes: 9 additions & 1 deletion packages/@aws-cdk/aws-route53/lib/hosted-zone-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import cdk = require('@aws-cdk/cdk');
*/
export interface IHostedZone extends cdk.IConstruct {
/**
* ID of this hosted zone
* ID of this hosted zone, such as "Z23ABC4XYZL05B"
*/
readonly hostedZoneId: string;

Expand All @@ -14,6 +14,14 @@ export interface IHostedZone extends cdk.IConstruct {
*/
readonly zoneName: string;

/**
* Returns the set of name servers for the specific hosted zone. For example:
* ns1.example.com.
*
* This attribute will be undefined for private hosted zones or hosted zones imported from another stack.
*/
readonly hostedZoneNameServers?: string[];

/**
* Export the hosted zone
*/
Expand Down
159 changes: 74 additions & 85 deletions packages/@aws-cdk/aws-route53/lib/hosted-zone.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,125 @@
import ec2 = require('@aws-cdk/aws-ec2');
import cdk = require('@aws-cdk/cdk');
import { HostedZoneImportProps, IHostedZone } from './hosted-zone-ref';
import { CfnHostedZone, HostedZoneNameServers } from './route53.generated';
import { CfnHostedZone } from './route53.generated';
import { validateZoneName } from './util';

/**
* Properties of a new hosted zone
*/
export interface PublicHostedZoneProps {
export interface CommonHostedZoneProps {
/**
* The fully qualified domain name for the hosted zone
* The name of the domain. For resource record types that include a domain
* name, specify a fully qualified domain name.
*/
zoneName: string;

/**
* Any comments that you want to include about the hosted zone.
*
* @default no comment
* @default none
*/
comment?: string;

/**
* The Amazon Resource Name (ARN) for the log group that you want Amazon Route 53 to send query logs to.
*
* @default no DNS query logging
* @default disabled
*/
queryLogsLogGroupArn?: string;
}

export abstract class HostedZone extends cdk.Construct implements IHostedZone {
public static import(scope: cdk.Construct, id: string, props: HostedZoneImportProps): IHostedZone {
return new ImportedHostedZone(scope, id, props);
}

public abstract readonly hostedZoneId: string;
public abstract readonly zoneName: string;

public export(): HostedZoneImportProps {
return {
hostedZoneId: new cdk.Output(this, 'HostedZoneId', { value: this.hostedZoneId }).makeImportValue().toString(),
zoneName: this.zoneName,
};
}
}

/**
* Create a Route53 public hosted zone.
* Properties of a new hosted zone
*/
export class PublicHostedZone extends HostedZone {
export interface HostedZoneProps extends CommonHostedZoneProps {
/**
* Identifier of this hosted zone
* A VPC that you want to associate with this hosted zone. When you specify
* this property, a private hosted zone will be created.
*
* You can associate additional VPCs to this private zone using `addVpc(vpc)`.
*
* @default public (no VPCs associated)
*/
public readonly hostedZoneId: string;
vpcs?: ec2.IVpcNetwork[];
}

export class HostedZone extends cdk.Construct implements IHostedZone {
/**
* Fully qualified domain name for the hosted zone
* Imports a hosted zone from another stack.
*/
public static import(scope: cdk.Construct, id: string, props: HostedZoneImportProps): IHostedZone {
return new ImportedHostedZone(scope, id, props);
}

public readonly hostedZoneId: string;
public readonly zoneName: string;
public readonly hostedZoneNameServers?: string[];

/**
* Nameservers for this public hosted zone
* VPCs to which this hosted zone will be added
*/
public readonly nameServers: HostedZoneNameServers;
protected readonly vpcs = new Array<CfnHostedZone.VPCProperty>();

constructor(scope: cdk.Construct, id: string, props: PublicHostedZoneProps) {
constructor(scope: cdk.Construct, id: string, props: HostedZoneProps) {
super(scope, id);

validateZoneName(props.zoneName);

const hostedZone = new CfnHostedZone(this, 'Resource', {
...determineHostedZoneProps(props)
name: props.zoneName + '.',
hostedZoneConfig: props.comment ? { comment: props.comment } : undefined,
queryLoggingConfig: props.queryLogsLogGroupArn ? { cloudWatchLogsLogGroupArn: props.queryLogsLogGroupArn } : undefined,
vpcs: new cdk.Token(() => this.vpcs.length === 0 ? undefined : this.vpcs)
});

this.hostedZoneId = hostedZone.ref;
this.nameServers = hostedZone.hostedZoneNameServers;
this.hostedZoneNameServers = hostedZone.hostedZoneNameServers.toList();
this.zoneName = props.zoneName;

for (const vpc of props.vpcs || []) {
this.addVpc(vpc);
}
}

public export(): HostedZoneImportProps {
return {
hostedZoneId: new cdk.Output(this, 'HostedZoneId', { value: this.hostedZoneId }).makeImportValue(),
zoneName: this.zoneName,
};
}

/**
* Add another VPC to this private hosted zone.
*
* @param vpc the other VPC to add.
*/
public addVpc(vpc: ec2.IVpcNetwork) {
this.vpcs.push({ vpcId: vpc.vpcId, vpcRegion: new cdk.AwsRegion() });
}
}

// tslint:disable-next-line:no-empty-interface
export interface PublicHostedZoneProps extends CommonHostedZoneProps {

}

/**
* Properties for a private hosted zone.
* Create a Route53 public hosted zone.
*/
export interface PrivateHostedZoneProps extends PublicHostedZoneProps {
export class PublicHostedZone extends HostedZone {
constructor(scope: cdk.Construct, id: string, props: PublicHostedZoneProps) {
super(scope, id, props);
}

public addVpc(_vpc: ec2.IVpcNetwork) {
throw new Error('Cannot associate public hosted zones with a VPC');
}
}

export interface PrivateHostedZoneProps extends CommonHostedZoneProps {
/**
* One VPC that you want to associate with this hosted zone.
* A VPC that you want to associate with this hosted zone.
*
* Private hosted zones must be associated with at least one VPC. You can
* associated additional VPCs using `addVpc(vpc)`.
*/
vpc: ec2.IVpcNetwork;
}
Expand All @@ -95,65 +131,18 @@ export interface PrivateHostedZoneProps extends PublicHostedZoneProps {
* for the VPC you're configuring for private hosted zones.
*/
export class PrivateHostedZone extends HostedZone {
/**
* Identifier of this hosted zone
*/
public readonly hostedZoneId: string;

/**
* Fully qualified domain name for the hosted zone
*/
public readonly zoneName: string;

/**
* VPCs to which this hosted zone will be added
*/
private readonly vpcs: CfnHostedZone.VPCProperty[] = [];

constructor(scope: cdk.Construct, id: string, props: PrivateHostedZoneProps) {
super(scope, id);

validateZoneName(props.zoneName);

const hostedZone = new CfnHostedZone(this, 'Resource', {
vpcs: new cdk.Token(() => this.vpcs ? this.vpcs : undefined),
...determineHostedZoneProps(props)
});

this.hostedZoneId = hostedZone.ref;
this.zoneName = props.zoneName;
super(scope, id, props);

this.addVpc(props.vpc);
}

/**
* Add another VPC to this private hosted zone.
*
* @param vpc the other VPC to add.
*/
public addVpc(vpc: ec2.IVpcNetwork) {
this.vpcs.push(toVpcProperty(vpc));
}
}

function toVpcProperty(vpc: ec2.IVpcNetwork): CfnHostedZone.VPCProperty {
return { vpcId: vpc.vpcId, vpcRegion: new cdk.AwsRegion() };
}

function determineHostedZoneProps(props: PublicHostedZoneProps) {
const name = props.zoneName + '.';
const hostedZoneConfig = props.comment ? { comment: props.comment } : undefined;
const queryLoggingConfig = props.queryLogsLogGroupArn ? { cloudWatchLogsLogGroupArn: props.queryLogsLogGroupArn } : undefined;

return { name, hostedZoneConfig, queryLoggingConfig };
}

/**
* Imported hosted zone
*/
class ImportedHostedZone extends cdk.Construct implements IHostedZone {
public readonly hostedZoneId: string;

public readonly zoneName: string;

constructor(scope: cdk.Construct, name: string, private readonly props: HostedZoneImportProps) {
Expand Down
47 changes: 47 additions & 0 deletions packages/@aws-cdk/aws-route53/lib/records/cname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Construct } from '@aws-cdk/cdk';
import { IHostedZone } from '../hosted-zone-ref';
import { CfnRecordSet } from '../route53.generated';
import { determineFullyQualifiedDomainName } from './_util';

export interface CnameRecordProps {
/**
* The hosted zone in which to define the new TXT record.
*/
zone: IHostedZone;

/**
* The domain name for this record set.
*/
recordName: string;

/**
* The value for this record set.
*/
recordValue: string;

/**
* The resource record cache time to live (TTL) in seconds.
*
* @default 1800 seconds
*/
ttl?: number;
}

/**
* A DNS CNAME record
*/
export class CnameRecord extends Construct {
constructor(scope: Construct, id: string, props: CnameRecordProps) {
super(scope, id);

const ttl = props.ttl === undefined ? 1800 : props.ttl;

new CfnRecordSet(this, 'Resource', {
hostedZoneId: props.zone.hostedZoneId,
name: determineFullyQualifiedDomainName(props.recordName, props.zone),
type: 'CNAME',
resourceRecords: [ props.recordValue ],
ttl: ttl.toString(),
});
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-route53/lib/records/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './alias';
export * from './txt';
export * from './cname';
export * from './zone-delegation';
24 changes: 20 additions & 4 deletions packages/@aws-cdk/aws-route53/lib/records/txt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,35 @@ import { IHostedZone } from '../hosted-zone-ref';
import { CfnRecordSet } from '../route53.generated';
import { determineFullyQualifiedDomainName } from './_util';

export interface TXTRecordProps {
export interface TxtRecordProps {
/**
* The hosted zone in which to define the new TXT record.
*/
zone: IHostedZone;

/**
* The domain name for this record set.
*/
recordName: string;

/**
* The value for this record set.
*/
recordValue: string;
/** @default 1800 seconds */

/**
* The resource record cache time to live (TTL) in seconds.
*
* @default 1800 seconds
*/
ttl?: number;
}

/**
* A DNS TXT record
*/
export class TXTRecord extends Construct {
constructor(scope: Construct, id: string, props: TXTRecordProps) {
export class TxtRecord extends Construct {
constructor(scope: Construct, id: string, props: TxtRecordProps) {
super(scope, id);

// JSON.stringify conveniently wraps strings in " and escapes ".
Expand Down
6 changes: 0 additions & 6 deletions packages/@aws-cdk/aws-route53/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,5 @@
},
"engines": {
"node": ">= 8.10.0"
},
"awslint": {
"exclude": [
"resource-attribute:@aws-cdk/aws-route53.IHostedZone.hostedZoneNameServers",
"resource-props:@aws-cdk/aws-route53.HostedZoneProps"
]
}
}
Loading

0 comments on commit 17eddd1

Please sign in to comment.