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-route53-targets): add global accelerator target to route53 alias targets #13407

Merged
merged 15 commits into from Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-route53-targets/README.md
Expand Up @@ -63,6 +63,19 @@ This library contains Route53 Alias Record targets for:

For example, if the Amazon-provided DNS for the load balancer is `ALB-xxxxxxx.us-west-2.elb.amazonaws.com`, CDK will create alias target in Route 53 will be `dualstack.ALB-xxxxxxx.us-west-2.elb.amazonaws.com`.

* GlobalAccelerator

```ts
new route53.ARecord(stack, 'AliasRecord', {
zone,
target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget(accelerator)),
// or - route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget('xyz.awsglobalaccelerator.com')),
});
```

**Important:** If you pass the global accelerator target a string rather than an instance of IAccelerator, ensure that the string is a valid domain name of an existing Global Accelerator instance.
See [the documentation on DNS addressing](https://docs.aws.amazon.com/global-accelerator/latest/dg/dns-addressing-custom-domains.dns-addressing.html) with Global Accelerator for more info.

* InterfaceVpcEndpoints

**Important:** Based on the CFN docs for VPCEndpoints - [see here](attrDnsEntries) - the attributes returned for DnsEntries in CloudFormation is a combination of the hosted zone ID and the DNS name. The entries are ordered as follows: regional public DNS, zonal public DNS, private DNS, and wildcard DNS. This order is not enforced for AWS Marketplace services, and therefore this CDK construct is ONLY guaranteed to work with non-marketplace services.
Expand Down
@@ -0,0 +1,36 @@
import * as globalaccelerator from '@aws-cdk/aws-globalaccelerator';
import * as route53 from '@aws-cdk/aws-route53';

/**
* Use a Global Accelerator domain name as an alias record target.
*/
export class GlobalAcceleratorTarget implements route53.IAliasRecordTarget {
/**
* The hosted zone Id if using an alias record in Route53.
* This value never changes.
* Ref: https://docs.aws.amazon.com/general/latest/gr/global_accelerator.html
*/
public static readonly GLOBAL_ACCELERATOR_ZONE_ID = 'Z2BJ6XQ5FK7U4H';

/**
* Create an Alias Target for a Global Accelerator.
*
* If passing a string value, it must be a valid domain name for an existing Global Accelerator. e.g. xyz.awsglobalaccelerator.com
* If passing an instance of an accelerator created within CDK, the accelerator.dnsName property will be used as the target.
*/
constructor(private readonly accelerator: string | globalaccelerator.IAccelerator) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we can't use type unions in our public APIs as they cannot be strongly-modeled in all languages the CDK supports.

Solutions:

  1. Typically we recommend using static methods instead (e.g., GlobalAcceleratorTarget.domain('xyz.awsglobalaccelerator.com'), GlobalAcceleratorTarget.accelerator(accelerator)).
  2. Within this package, it looks like maybe just creating a second class like ApiGateway/ApiGatewayDomain is an existing, alternative pattern. So you could create GlobalAcceleratorTarget and GlobalAcceleratorDomainTarget.
  3. Final alternative would be to just create this GlobalAcceleratorTarget that accepts an IAccelerator, and add the GlobalAcceleratorDomainTarget (that takes the domain name/string) later based on feedback from the community.

I think given the existing patterns in this module options 2 or 3 are the principal of least surprise; I'm happy either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with option 2. I think it actually makes the code a little cleaner, although it might not be as intuitive to locate and use.

}

bind(_record: route53.IRecordSet): route53.AliasRecordTargetConfig {
let acceleratorDomainName;
if (typeof this.accelerator === 'string') {
acceleratorDomainName = this.accelerator;
} else {
acceleratorDomainName = this.accelerator.dnsName;
}
return {
hostedZoneId: GlobalAcceleratorTarget.GLOBAL_ACCELERATOR_ZONE_ID,
dnsName: acceleratorDomainName,
};
}
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-route53-targets/lib/index.ts
Expand Up @@ -6,3 +6,4 @@ export * from './cloudfront-target';
export * from './load-balancer-target';
export * from './interface-vpc-endpoint-target';
export * from './userpool-domain';
export * from './global-accelerator-target';
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-route53-targets/package.json
Expand Up @@ -80,6 +80,7 @@
"@aws-cdk/aws-elasticloadbalancing": "0.0.0",
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-globalaccelerator": "0.0.0",
"@aws-cdk/aws-route53": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/core": "0.0.0",
Expand All @@ -96,6 +97,7 @@
"@aws-cdk/aws-elasticloadbalancing": "0.0.0",
"@aws-cdk/aws-elasticloadbalancingv2": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-globalaccelerator": "0.0.0",
"@aws-cdk/aws-route53": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/core": "0.0.0",
Expand Down
@@ -0,0 +1,58 @@
import '@aws-cdk/assert/jest';
import * as globalaccelerator from '@aws-cdk/aws-globalaccelerator';
import * as route53 from '@aws-cdk/aws-route53';
import { Stack } from '@aws-cdk/core';
import * as targets from '../lib';

test('GlobalAcceleratorTarget exposes a public constant of the zone id', () => {
expect(targets.GlobalAcceleratorTarget.GLOBAL_ACCELERATOR_ZONE_ID).toStrictEqual('Z2BJ6XQ5FK7U4H');
});

test('GlobalAcceleratorTarget creates an alias resource with a string domain name', () => {
// GIVEN
const stack = new Stack();
const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' });

// WHEN
new route53.ARecord(stack, 'GlobalAcceleratorAlias', {
target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget('xyz.awsglobalaccelerator.com')),
recordName: 'test',
zone,
});

// THEN
expect(stack).toHaveResource('AWS::Route53::RecordSet', {
AliasTarget: {
DNSName: 'xyz.awsglobalaccelerator.com',
HostedZoneId: 'Z2BJ6XQ5FK7U4H',
},
});
});

test('GlobalAcceleratorTarget creates an alias resource with a Global Accelerator reference domain name', () => {
// GIVEN
const stack = new Stack();
const accelerator = new globalaccelerator.Accelerator(stack, 'Accelerator');
const logicalId = stack.getLogicalId(<globalaccelerator.CfnAccelerator>accelerator.node.defaultChild);
const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' });

// WHEN
new route53.ARecord(stack, 'GlobalAcceleratorAlias', {
target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget(accelerator)),
recordName: 'test',
zone,
});

// THEN
expect(stack).toHaveResource('AWS::Route53::RecordSet', {
AliasTarget: {
DNSName: {
'Fn::GetAtt': [
logicalId,
'DnsName',
],
},
HostedZoneId: 'Z2BJ6XQ5FK7U4H',
},
});
});
@@ -0,0 +1,52 @@
{
"Resources": {
"Accelerator8EB0B6B1": {
"Type": "AWS::GlobalAccelerator::Accelerator",
"Properties": {
"Name": "aws-cdk-globalaccelerator-integ",
"Enabled": true
}
},
"HostedZoneDB99F866": {
"Type": "AWS::Route53::HostedZone",
"Properties": {
"Name": "test.public."
}
},
"LocalGlobalAcceleratorAlias18B4A87A": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
"Name": "test-local.test.public.",
"Type": "A",
"AliasTarget": {
"DNSName": {
"Fn::GetAtt": [
"Accelerator8EB0B6B1",
"DnsName"
]
},
"HostedZoneId": "Z2BJ6XQ5FK7U4H"
},
"Comment": "Alias to the locally created Global Accelerator",
"HostedZoneId": {
"Ref": "HostedZoneDB99F866"
}
}
},
"ExistingGlobalAcceleratorAlias7ACF888C": {
"Type": "AWS::Route53::RecordSet",
"Properties": {
"Name": "test-existing.test.public.",
"Type": "A",
"AliasTarget": {
"DNSName": "someexisting.awsglobalaccelerator.com",
"HostedZoneId": "Z2BJ6XQ5FK7U4H"
},
"Comment": "Alias to the an existing Global Accelerator",
"HostedZoneId": {
"Ref": "HostedZoneDB99F866"
}
}
}
}
}
@@ -0,0 +1,31 @@
#!/usr/bin/env node
import * as globalaccelerator from '@aws-cdk/aws-globalaccelerator';
import * as route53 from '@aws-cdk/aws-route53';
import * as cdk from '@aws-cdk/core';
import * as targets from '../lib';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'aws-cdk-globalaccelerator-integ');

let accelerator = new globalaccelerator.Accelerator(stack, 'Accelerator', {
acceleratorName: `${stack.stackName}`,
enabled: true,
});

const zone = new route53.PublicHostedZone(stack, 'HostedZone', { zoneName: 'test.public' });

new route53.ARecord(stack, 'LocalGlobalAcceleratorAlias', {
comment: 'Alias to the locally created Global Accelerator',
target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget(accelerator)),
recordName: 'test-local',
zone,
});

new route53.ARecord(stack, 'ExistingGlobalAcceleratorAlias', {
njlynch marked this conversation as resolved.
Show resolved Hide resolved
comment: 'Alias to the an existing Global Accelerator',
target: route53.RecordTarget.fromAlias(new targets.GlobalAcceleratorTarget('someexisting.awsglobalaccelerator.com')),
recordName: 'test-existing',
zone,
});

app.synth();