From 3e9abc3b2674967905e0ddcdfd9f7c6afadba143 Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 21 May 2021 14:15:05 +0200 Subject: [PATCH] feat(route53-targets): record target Add an alias target for a Route53 record. Closes #14800 --- .../@aws-cdk/aws-route53-targets/README.md | 9 ++++ .../lib/api-gateway-domain-name.ts | 2 +- .../lib/api-gatewayv2-domain-name.ts | 4 +- .../lib/bucket-website-target.ts | 2 +- .../lib/classic-load-balancer-target.ts | 2 +- .../lib/cloudfront-target.ts | 2 +- .../lib/global-accelerator-target.ts | 2 +- .../@aws-cdk/aws-route53-targets/lib/index.ts | 1 + .../lib/interface-vpc-endpoint-target.ts | 2 +- .../lib/load-balancer-target.ts | 2 +- .../aws-route53-targets/lib/route53-record.ts | 19 +++++++++ .../lib/userpool-domain.ts | 4 +- .../test/integ.route53-record.expected.json | 42 +++++++++++++++++++ .../test/integ.route53-record.ts | 30 +++++++++++++ .../test/route53-record.test.ts | 32 ++++++++++++++ .../aws-route53/lib/alias-record-target.ts | 3 +- .../@aws-cdk/aws-route53/lib/record-set.ts | 2 +- 17 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts create mode 100644 packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json create mode 100644 packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts create mode 100644 packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts diff --git a/packages/@aws-cdk/aws-route53-targets/README.md b/packages/@aws-cdk/aws-route53-targets/README.md index 6ab9c8822cae9..07be5b2e363d0 100644 --- a/packages/@aws-cdk/aws-route53-targets/README.md +++ b/packages/@aws-cdk/aws-route53-targets/README.md @@ -120,4 +120,13 @@ See [the Developer Guide](https://docs.aws.amazon.com/Route53/latest/DeveloperGu }); ``` +* Route 53 record + + ```ts + new route53.ARecord(this, 'AliasRecord', { + zone, + target: route53.RecordTarget.fromAlias(new targets.Route53RecordTarget(record)), + }); + ``` + See the documentation of `@aws-cdk/aws-route53` for more information. diff --git a/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts b/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts index 5aa40d5a52f10..c87e26568d6b8 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/api-gateway-domain-name.ts @@ -10,7 +10,7 @@ import * as route53 from '@aws-cdk/aws-route53'; export class ApiGatewayDomain implements route53.IAliasRecordTarget { constructor(private readonly domainName: apig.IDomainName) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: this.domainName.domainNameAliasDomainName, hostedZoneId: this.domainName.domainNameAliasHostedZoneId, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts index 6aea6f72de59e..962315ed2ab8c 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/api-gatewayv2-domain-name.ts @@ -10,10 +10,10 @@ export class ApiGatewayv2DomainProperties implements route53.IAliasRecordTarget */ constructor(private readonly regionalDomainName: string, private readonly regionalHostedZoneId: string) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: this.regionalDomainName, hostedZoneId: this.regionalHostedZoneId, }; } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts index 54df0f33e450f..a4497826828f1 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/bucket-website-target.ts @@ -10,7 +10,7 @@ export class BucketWebsiteTarget implements route53.IAliasRecordTarget { constructor(private readonly bucket: s3.IBucket) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { const { region } = Stack.of(this.bucket.stack); if (Token.isUnresolved(region)) { diff --git a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts index 8b25fbe4f331e..54bf900b1f6c8 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/classic-load-balancer-target.ts @@ -8,7 +8,7 @@ export class ClassicLoadBalancerTarget implements route53.IAliasRecordTarget { constructor(private readonly loadBalancer: elb.LoadBalancer) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneNameId, dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts index d11e0c9de2324..0d6f57dcf9dd6 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/cloudfront-target.ts @@ -40,7 +40,7 @@ export class CloudFrontTarget implements route53.IAliasRecordTarget { constructor(private readonly distribution: cloudfront.IDistribution) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: CloudFrontTarget.getHostedZoneId(this.distribution), dnsName: this.distribution.distributionDomainName, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts index d80aaaa140b1b..e9093265ea228 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/global-accelerator-target.ts @@ -19,7 +19,7 @@ export class GlobalAcceleratorDomainTarget implements route53.IAliasRecordTarget constructor(private readonly acceleratorDomainName: string) { } - bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: GlobalAcceleratorTarget.GLOBAL_ACCELERATOR_ZONE_ID, dnsName: this.acceleratorDomainName, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/index.ts b/packages/@aws-cdk/aws-route53-targets/lib/index.ts index 5c8b86fb959c1..92f1d9ee08d25 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/index.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/index.ts @@ -7,3 +7,4 @@ export * from './load-balancer-target'; export * from './interface-vpc-endpoint-target'; export * from './userpool-domain'; export * from './global-accelerator-target'; +export * from './route53-record'; diff --git a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts index 40b0902cd236a..1882a5225b959 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/interface-vpc-endpoint-target.ts @@ -11,7 +11,7 @@ export class InterfaceVpcEndpointTarget implements route53.IAliasRecordTarget { this.cfnVpcEndpoint = this.vpcEndpoint.node.findChild('Resource') as ec2.CfnVPCEndpoint; } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { dnsName: cdk.Fn.select(1, cdk.Fn.split(':', cdk.Fn.select(0, this.cfnVpcEndpoint.attrDnsEntries))), hostedZoneId: cdk.Fn.select(0, cdk.Fn.split(':', cdk.Fn.select(0, this.cfnVpcEndpoint.attrDnsEntries))), diff --git a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts index d398256c57f8b..d3bb10280690e 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/load-balancer-target.ts @@ -8,7 +8,7 @@ export class LoadBalancerTarget implements route53.IAliasRecordTarget { constructor(private readonly loadBalancer: elbv2.ILoadBalancerV2) { } - public bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig { + public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { return { hostedZoneId: this.loadBalancer.loadBalancerCanonicalHostedZoneId, dnsName: `dualstack.${this.loadBalancer.loadBalancerDnsName}`, diff --git a/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts b/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts new file mode 100644 index 0000000000000..e8a042c7e287c --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/lib/route53-record.ts @@ -0,0 +1,19 @@ +import * as route53 from '@aws-cdk/aws-route53'; + +/** + * Use another Route 53 record as an alias record target + */ +export class Route53RecordTarget implements route53.IAliasRecordTarget { + constructor(private readonly record: route53.IRecordSet) { + } + + public bind(_record: route53.IRecordSet, zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + if (!zone) { // zone introduced as optional to avoid a breaking change + throw new Error('Cannot bind to record without a zone'); + } + return { + dnsName: this.record.domainName, + hostedZoneId: zone.hostedZoneId, + }; + } +} diff --git a/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts b/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts index 20b04f4e64f3a..a4e5c585f8927 100644 --- a/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts +++ b/packages/@aws-cdk/aws-route53-targets/lib/userpool-domain.ts @@ -1,5 +1,5 @@ import { UserPoolDomain } from '@aws-cdk/aws-cognito'; -import { AliasRecordTargetConfig, IAliasRecordTarget, IRecordSet } from '@aws-cdk/aws-route53'; +import { AliasRecordTargetConfig, IAliasRecordTarget, IHostedZone, IRecordSet } from '@aws-cdk/aws-route53'; import { CloudFrontTarget } from './cloudfront-target'; /** @@ -9,7 +9,7 @@ export class UserPoolDomainTarget implements IAliasRecordTarget { constructor(private readonly domain: UserPoolDomain) { } - public bind(_record: IRecordSet): AliasRecordTargetConfig { + public bind(_record: IRecordSet, _zone?: IHostedZone): AliasRecordTargetConfig { return { dnsName: this.domain.cloudFrontDomainName, hostedZoneId: CloudFrontTarget.getHostedZoneId(this.domain), diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json new file mode 100644 index 0000000000000..9394dd9bb11c1 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.expected.json @@ -0,0 +1,42 @@ +{ + "Resources": { + "HostedZoneDB99F866": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "cdk-integ.com." + } + }, + "WWW9F8609DA": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "www.cdk-integ.com.", + "Type": "A", + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + }, + "ResourceRecords": [ + "1.2.3.4" + ], + "TTL": "1800" + } + }, + "Alias325C5727": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "cdk-integ.com.", + "Type": "A", + "AliasTarget": { + "DNSName": { + "Ref": "WWW9F8609DA" + }, + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + }, + "HostedZoneId": { + "Ref": "HostedZoneDB99F866" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts new file mode 100644 index 0000000000000..aeec73fa19233 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/integ.route53-record.ts @@ -0,0 +1,30 @@ +#!/usr/bin/env node +import * as route53 from '@aws-cdk/aws-route53'; +import { App, Stack } from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import * as targets from '../lib'; + +class TestStack extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + + const zone = new route53.PublicHostedZone(this, 'HostedZone', { + zoneName: 'cdk-integ.com', + }); + + const www = new route53.ARecord(this, 'WWW', { + zone, + recordName: 'www.cdk-integ.com', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4'), + }); + + new route53.ARecord(this, 'Alias', { + zone, + target: route53.RecordTarget.fromAlias(new targets.Route53RecordTarget(www)), + }); + } +} + +const app = new App(); +new TestStack(app, 'aws-cdk-r53-record-alias-target-integ'); +app.synth(); diff --git a/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts b/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts new file mode 100644 index 0000000000000..9b9bc7585a433 --- /dev/null +++ b/packages/@aws-cdk/aws-route53-targets/test/route53-record.test.ts @@ -0,0 +1,32 @@ +import '@aws-cdk/assert-internal/jest'; +import { ARecord, PublicHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; +import { Stack } from '@aws-cdk/core'; +import { Route53RecordTarget } from '../lib'; + +test('use another route 53 record as record target', () => { + // GIVEN + const stack = new Stack(); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' }); + const record = new ARecord(zone, 'Record', { + zone, + target: RecordTarget.fromIpAddresses('1.2.3.4'), + }); + + // WHEN + new ARecord(zone, 'Alias', { + zone, + target: RecordTarget.fromAlias(new Route53RecordTarget(record)), + }); + + // THEN + expect(stack).toHaveResource('AWS::Route53::RecordSet', { + AliasTarget: { + DNSName: { + Ref: 'HostedZoneRecordB6AB510D', + }, + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + }, + }); +}); diff --git a/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts b/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts index 6e04daa0f565e..e9c2248f0b470 100644 --- a/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts +++ b/packages/@aws-cdk/aws-route53/lib/alias-record-target.ts @@ -1,3 +1,4 @@ +import { IHostedZone } from './hosted-zone-ref'; import { IRecordSet } from './record-set'; /** @@ -8,7 +9,7 @@ export interface IAliasRecordTarget { /** * Return hosted zone ID and DNS name, usable for Route53 alias targets */ - bind(record: IRecordSet): AliasRecordTargetConfig; + bind(record: IRecordSet, zone?: IHostedZone): AliasRecordTargetConfig; } /** diff --git a/packages/@aws-cdk/aws-route53/lib/record-set.ts b/packages/@aws-cdk/aws-route53/lib/record-set.ts index 57f93931c2097..29e40f626a65d 100644 --- a/packages/@aws-cdk/aws-route53/lib/record-set.ts +++ b/packages/@aws-cdk/aws-route53/lib/record-set.ts @@ -219,7 +219,7 @@ export class RecordSet extends Resource implements IRecordSet { name: determineFullyQualifiedDomainName(props.recordName || props.zone.zoneName, props.zone), type: props.recordType, resourceRecords: props.target.values, - aliasTarget: props.target.aliasTarget && props.target.aliasTarget.bind(this), + aliasTarget: props.target.aliasTarget && props.target.aliasTarget.bind(this, props.zone), ttl, comment: props.comment, });