From 6bd9a2d1293b94e83cb6fe9b3768155f646d9066 Mon Sep 17 00:00:00 2001 From: Julian Michel Date: Mon, 24 Jul 2023 20:34:52 +0200 Subject: [PATCH] feat(route53): support geolocation routing (#26383) Add support for [geolocation routing](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy-geo.html) in Route53 RecordSets. This PR adds attribute `geoLocation` to `RecordSetOptions` and new class `GeoLocation`. This enables developers to use geolocation routing. The new feature can be used like this (more examples in README): ```ts new route53.ARecord(this, 'ARecordGeoLocationContinent', { zone: myZone, target: route53.RecordTarget.fromIpAddresses('1.2.3.0', '5.6.7.0'), geoLocation: route53.GeoLocation.continent('EU'), // Europe }); ``` Closes #9478. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../aws-cdk-route53-integ.assets.json | 6 +- .../aws-cdk-route53-integ.template.json | 189 +++++++++--- .../test/integ.route53.js.snapshot/cdk.out | 2 +- .../test/integ.route53.js.snapshot/integ.json | 2 +- .../integ.route53.js.snapshot/manifest.json | 43 ++- .../test/integ.route53.js.snapshot/tree.json | 281 +++++++++++++++--- .../test/aws-route53/test/integ.route53.ts | 36 ++- packages/aws-cdk-lib/aws-route53/README.md | 34 +++ .../aws-route53/lib/geo-location.ts | 114 +++++++ packages/aws-cdk-lib/aws-route53/lib/index.ts | 1 + .../aws-cdk-lib/aws-route53/lib/record-set.ts | 26 ++ .../aws-route53/test/geo-location.test.ts | 43 +++ .../aws-route53/test/record-set.test.ts | 177 +++++++++++ 13 files changed, 853 insertions(+), 101 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-route53/lib/geo-location.ts create mode 100644 packages/aws-cdk-lib/aws-route53/test/geo-location.test.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.assets.json index ec5946625811f..ba1f46d681a62 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.assets.json @@ -1,7 +1,7 @@ { - "version": "31.0.0", + "version": "32.0.0", "files": { - "f9c954c8023429cbccaf7169883a3b5bb6ecd00c97f9931179eb65a405f68dd4": { + "1e880e43b77aa38d085edaa0d9718a0b2d38584b11562fbbfc3c259c42b9d8b1": { "source": { "path": "aws-cdk-route53-integ.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "f9c954c8023429cbccaf7169883a3b5bb6ecd00c97f9931179eb65a405f68dd4.json", + "objectKey": "1e880e43b77aa38d085edaa0d9718a0b2d38584b11562fbbfc3c259c42b9d8b1.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.template.json index c38a0fb8a1572..c869b4f924201 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/aws-cdk-route53-integ.template.json @@ -18,9 +18,6 @@ "VPCPublicSubnet1SubnetB4246D30": { "Type": "AWS::EC2::Subnet", "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - }, "AvailabilityZone": { "Fn::Select": [ 0, @@ -44,21 +41,24 @@ "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PublicSubnet1" } - ] + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "VPCPublicSubnet1RouteTableFEE4B781": { "Type": "AWS::EC2::RouteTable", "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - }, "Tags": [ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PublicSubnet1" } - ] + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "VPCPublicSubnet1RouteTableAssociation0B0896DC": { @@ -75,12 +75,12 @@ "VPCPublicSubnet1DefaultRoute91CEF279": { "Type": "AWS::EC2::Route", "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": { "Ref": "VPCIGWB7E252D3" + }, + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" } }, "DependsOn": [ @@ -102,15 +102,15 @@ "VPCPublicSubnet1NATGatewayE0556630": { "Type": "AWS::EC2::NatGateway", "Properties": { - "SubnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "AllocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "Tags": [ { "Key": "Name", @@ -126,9 +126,6 @@ "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - }, "AvailabilityZone": { "Fn::Select": [ 0, @@ -152,21 +149,24 @@ "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PrivateSubnet1" } - ] + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "VPCPrivateSubnet1RouteTableBE8A6027": { "Type": "AWS::EC2::RouteTable", "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - }, "Tags": [ { "Key": "Name", "Value": "aws-cdk-route53-integ/VPC/PrivateSubnet1" } - ] + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "VPCPrivateSubnet1RouteTableAssociation347902D1": { @@ -183,12 +183,12 @@ "VPCPrivateSubnet1DefaultRouteAE1D6490": { "Type": "AWS::EC2::Route", "Properties": { - "RouteTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" - }, "DestinationCidrBlock": "0.0.0.0/0", "NatGatewayId": { "Ref": "VPCPublicSubnet1NATGatewayE0556630" + }, + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" } } }, @@ -206,11 +206,11 @@ "VPCVPCGW99B986DC": { "Type": "AWS::EC2::VPCGatewayAttachment", "Properties": { - "VpcId": { - "Ref": "VPCB9E5F0B4" - }, "InternetGatewayId": { "Ref": "VPCIGWB7E252D3" + }, + "VpcId": { + "Ref": "VPCB9E5F0B4" } } }, @@ -233,16 +233,16 @@ "PrivateZoneTXT83BB83CE": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "_foo.cdk.local.", - "Type": "TXT", "HostedZoneId": { "Ref": "PrivateZone27242E85" }, + "Name": "_foo.cdk.local.", "ResourceRecords": [ "\"Bar!\"", "\"Baz?\"" ], - "TTL": "60" + "TTL": "60", + "Type": "TXT" } }, "PublicZone2E1C4E34": { @@ -254,18 +254,18 @@ "PublicZonecdktestsubcdktest83558650": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "sub.cdk.test.", - "Type": "NS", "HostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "Name": "sub.cdk.test.", "ResourceRecords": { "Fn::GetAtt": [ "PublicSubZoneDBD26A0A", "NameServers" ] }, - "TTL": "172800" + "TTL": "172800", + "Type": "NS" } }, "PublicSubZoneDBD26A0A": { @@ -283,58 +283,155 @@ "CNAMEC70A2D52": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "www.cdk.local.", - "Type": "CNAME", "HostedZoneId": { "Ref": "PrivateZone27242E85" }, + "Name": "www.cdk.local.", "ResourceRecords": [ "server" ], - "TTL": "1800" + "TTL": "1800", + "Type": "CNAME" } }, "ACCC8ACD5": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "test.cdk.local.", - "Type": "A", "HostedZoneId": { "Ref": "PrivateZone27242E85" }, + "Name": "test.cdk.local.", "ResourceRecords": [ "1.2.3.4", "5.6.7.8" ], - "TTL": "1800" + "TTL": "1800", + "Type": "A" + } + }, + "GeoLocationContinentAEA331ED": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "GeoLocation": { + "ContinentCode": "EU" + }, + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "Name": "geolocation.cdk.local.", + "ResourceRecords": [ + "1.2.3.0", + "5.6.7.0" + ], + "SetIdentifier": "GEO_CONTINENT_EU", + "TTL": "1800", + "Type": "A" + } + }, + "GeoLocationCountry523431F6": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "GeoLocation": { + "CountryCode": "DE" + }, + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "Name": "geolocation.cdk.local.", + "ResourceRecords": [ + "1.2.3.1", + "5.6.7.1" + ], + "SetIdentifier": "GEO_COUNTRY_DE", + "TTL": "1800", + "Type": "A" + } + }, + "GeoLocationSubDividion2CB12CFC": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "GeoLocation": { + "CountryCode": "US", + "SubdivisionCode": "WA" + }, + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "Name": "geolocation.cdk.local.", + "ResourceRecords": [ + "1.2.3.2", + "5.6.7.2" + ], + "SetIdentifier": "GEO_COUNTRY_US_SUBDIVISION_WA", + "TTL": "1800", + "Type": "A" + } + }, + "GeoLocationSubDividionUA778564B1": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "GeoLocation": { + "CountryCode": "UA", + "SubdivisionCode": "30" + }, + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "Name": "cdk.local.", + "ResourceRecords": [ + "1.2.3.4", + "5.6.7.4" + ], + "SetIdentifier": "GEO_COUNTRY_UA_SUBDIVISION_30", + "TTL": "1800", + "Type": "A" + } + }, + "GeoLocationDefaultF2DE9058": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "GeoLocation": { + "CountryCode": "*" + }, + "HostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "Name": "geolocation.cdk.local.", + "ResourceRecords": [ + "1.2.3.3", + "5.6.7.3" + ], + "SetIdentifier": "GEO_COUNTRY_*", + "TTL": "1800", + "Type": "A" } }, "CaaAmazon40DF725F": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "cdk.test.", - "Type": "CAA", "HostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "Name": "cdk.test.", "ResourceRecords": [ "0 issue \"amazon.com\"" ], - "TTL": "1800" + "TTL": "1800", + "Type": "CAA" } }, "TXT0D5C5ACF": { "Type": "AWS::Route53::RecordSet", "Properties": { - "Name": "cdk.test.", - "Type": "TXT", "HostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "Name": "cdk.test.", "ResourceRecords": [ "\"this is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long s\"\"tring\"" ], - "TTL": "1800" + "TTL": "1800", + "Type": "TXT" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/cdk.out index 7925065efbcc4..f0b901e7c06e5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"31.0.0"} \ No newline at end of file +{"version":"32.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/integ.json index 01fbad6cec914..91964b70e6260 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "31.0.0", + "version": "32.0.0", "testCases": { "integ.route53": { "stacks": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/manifest.json index ac2d57e394d60..59cae3a752f7f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "31.0.0", + "version": "32.0.0", "artifacts": { "aws-cdk-route53-integ.assets": { "type": "cdk:asset-manifest", @@ -17,7 +17,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/f9c954c8023429cbccaf7169883a3b5bb6ecd00c97f9931179eb65a405f68dd4.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/1e880e43b77aa38d085edaa0d9718a0b2d38584b11562fbbfc3c259c42b9d8b1.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -159,6 +159,36 @@ "data": "ACCC8ACD5" } ], + "/aws-cdk-route53-integ/GeoLocationContinent/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationContinentAEA331ED" + } + ], + "/aws-cdk-route53-integ/GeoLocationCountry/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationCountry523431F6" + } + ], + "/aws-cdk-route53-integ/GeoLocationSubDividion/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationSubDividion2CB12CFC" + } + ], + "/aws-cdk-route53-integ/GeoLocationSubDividionUA/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationSubDividionUA778564B1" + } + ], + "/aws-cdk-route53-integ/GeoLocationDefault/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationDefaultF2DE9058" + } + ], "/aws-cdk-route53-integ/CaaAmazon/Resource": [ { "type": "aws:cdk:logicalId", @@ -194,6 +224,15 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "GeoLocationSubDividionUSA98E93D0": [ + { + "type": "aws:cdk:logicalId", + "data": "GeoLocationSubDividionUSA98E93D0", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "aws-cdk-route53-integ" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/tree.json index 69e67a98ab3ab..447187714f427 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.js.snapshot/tree.json @@ -45,9 +45,6 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", "aws:cdk:cloudformation:props": { - "vpcId": { - "Ref": "VPCB9E5F0B4" - }, "availabilityZone": { "Fn::Select": [ 0, @@ -71,7 +68,10 @@ "key": "Name", "value": "aws-cdk-route53-integ/VPC/PublicSubnet1" } - ] + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "constructInfo": { @@ -93,15 +93,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", "aws:cdk:cloudformation:props": { - "vpcId": { - "Ref": "VPCB9E5F0B4" - }, "tags": [ { "key": "Name", "value": "aws-cdk-route53-integ/VPC/PublicSubnet1" } - ] + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "constructInfo": { @@ -134,12 +134,12 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::Route", "aws:cdk:cloudformation:props": { - "routeTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, "destinationCidrBlock": "0.0.0.0/0", "gatewayId": { "Ref": "VPCIGWB7E252D3" + }, + "routeTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" } } }, @@ -174,15 +174,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::NatGateway", "aws:cdk:cloudformation:props": { - "subnetId": { - "Ref": "VPCPublicSubnet1SubnetB4246D30" - }, "allocationId": { "Fn::GetAtt": [ "VPCPublicSubnet1EIP6AD938E8", "AllocationId" ] }, + "subnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, "tags": [ { "key": "Name", @@ -212,9 +212,6 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::Subnet", "aws:cdk:cloudformation:props": { - "vpcId": { - "Ref": "VPCB9E5F0B4" - }, "availabilityZone": { "Fn::Select": [ 0, @@ -238,7 +235,10 @@ "key": "Name", "value": "aws-cdk-route53-integ/VPC/PrivateSubnet1" } - ] + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "constructInfo": { @@ -260,15 +260,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::RouteTable", "aws:cdk:cloudformation:props": { - "vpcId": { - "Ref": "VPCB9E5F0B4" - }, "tags": [ { "key": "Name", "value": "aws-cdk-route53-integ/VPC/PrivateSubnet1" } - ] + ], + "vpcId": { + "Ref": "VPCB9E5F0B4" + } } }, "constructInfo": { @@ -301,12 +301,12 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::Route", "aws:cdk:cloudformation:props": { - "routeTableId": { - "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" - }, "destinationCidrBlock": "0.0.0.0/0", "natGatewayId": { "Ref": "VPCPublicSubnet1NATGatewayE0556630" + }, + "routeTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" } } }, @@ -346,11 +346,11 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::EC2::VPCGatewayAttachment", "aws:cdk:cloudformation:props": { - "vpcId": { - "Ref": "VPCB9E5F0B4" - }, "internetGatewayId": { "Ref": "VPCIGWB7E252D3" + }, + "vpcId": { + "Ref": "VPCB9E5F0B4" } } }, @@ -403,16 +403,16 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "_foo.cdk.local.", - "type": "TXT", "hostedZoneId": { "Ref": "PrivateZone27242E85" }, + "name": "_foo.cdk.local.", "resourceRecords": [ "\"Bar!\"", "\"Baz?\"" ], - "ttl": "60" + "ttl": "60", + "type": "TXT" } }, "constructInfo": { @@ -460,18 +460,18 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "sub.cdk.test.", - "type": "NS", "hostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "name": "sub.cdk.test.", "resourceRecords": { "Fn::GetAtt": [ "PublicSubZoneDBD26A0A", "NameServers" ] }, - "ttl": "172800" + "ttl": "172800", + "type": "NS" } }, "constructInfo": { @@ -549,15 +549,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "www.cdk.local.", - "type": "CNAME", "hostedZoneId": { "Ref": "PrivateZone27242E85" }, + "name": "www.cdk.local.", "resourceRecords": [ "server" ], - "ttl": "1800" + "ttl": "1800", + "type": "CNAME" } }, "constructInfo": { @@ -581,16 +581,203 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "test.cdk.local.", - "type": "A", "hostedZoneId": { "Ref": "PrivateZone27242E85" }, + "name": "test.cdk.local.", "resourceRecords": [ "1.2.3.4", "5.6.7.8" ], - "ttl": "1800" + "ttl": "1800", + "type": "A" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnRecordSet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.ARecord", + "version": "0.0.0" + } + }, + "GeoLocationContinent": { + "id": "GeoLocationContinent", + "path": "aws-cdk-route53-integ/GeoLocationContinent", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-integ/GeoLocationContinent/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", + "aws:cdk:cloudformation:props": { + "geoLocation": { + "continentCode": "EU" + }, + "hostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "name": "geolocation.cdk.local.", + "resourceRecords": [ + "1.2.3.0", + "5.6.7.0" + ], + "setIdentifier": "GEO_CONTINENT_EU", + "ttl": "1800", + "type": "A" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnRecordSet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.ARecord", + "version": "0.0.0" + } + }, + "GeoLocationCountry": { + "id": "GeoLocationCountry", + "path": "aws-cdk-route53-integ/GeoLocationCountry", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-integ/GeoLocationCountry/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", + "aws:cdk:cloudformation:props": { + "geoLocation": { + "countryCode": "DE" + }, + "hostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "name": "geolocation.cdk.local.", + "resourceRecords": [ + "1.2.3.1", + "5.6.7.1" + ], + "setIdentifier": "GEO_COUNTRY_DE", + "ttl": "1800", + "type": "A" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnRecordSet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.ARecord", + "version": "0.0.0" + } + }, + "GeoLocationSubDividion": { + "id": "GeoLocationSubDividion", + "path": "aws-cdk-route53-integ/GeoLocationSubDividion", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-integ/GeoLocationSubDividion/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", + "aws:cdk:cloudformation:props": { + "geoLocation": { + "countryCode": "US", + "subdivisionCode": "WA" + }, + "hostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "name": "geolocation.cdk.local.", + "resourceRecords": [ + "1.2.3.2", + "5.6.7.2" + ], + "setIdentifier": "GEO_COUNTRY_US_SUBDIVISION_WA", + "ttl": "1800", + "type": "A" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnRecordSet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.ARecord", + "version": "0.0.0" + } + }, + "GeoLocationSubDividionUA": { + "id": "GeoLocationSubDividionUA", + "path": "aws-cdk-route53-integ/GeoLocationSubDividionUA", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-integ/GeoLocationSubDividionUA/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", + "aws:cdk:cloudformation:props": { + "geoLocation": { + "countryCode": "UA", + "subdivisionCode": "30" + }, + "hostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "name": "cdk.local.", + "resourceRecords": [ + "1.2.3.4", + "5.6.7.4" + ], + "setIdentifier": "GEO_COUNTRY_UA_SUBDIVISION_30", + "ttl": "1800", + "type": "A" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.CfnRecordSet", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_route53.ARecord", + "version": "0.0.0" + } + }, + "GeoLocationDefault": { + "id": "GeoLocationDefault", + "path": "aws-cdk-route53-integ/GeoLocationDefault", + "children": { + "Resource": { + "id": "Resource", + "path": "aws-cdk-route53-integ/GeoLocationDefault/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", + "aws:cdk:cloudformation:props": { + "geoLocation": { + "countryCode": "*" + }, + "hostedZoneId": { + "Ref": "PrivateZone27242E85" + }, + "name": "geolocation.cdk.local.", + "resourceRecords": [ + "1.2.3.3", + "5.6.7.3" + ], + "setIdentifier": "GEO_COUNTRY_*", + "ttl": "1800", + "type": "A" } }, "constructInfo": { @@ -614,15 +801,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "cdk.test.", - "type": "CAA", "hostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "name": "cdk.test.", "resourceRecords": [ "0 issue \"amazon.com\"" ], - "ttl": "1800" + "ttl": "1800", + "type": "CAA" } }, "constructInfo": { @@ -646,15 +833,15 @@ "attributes": { "aws:cdk:cloudformation:type": "AWS::Route53::RecordSet", "aws:cdk:cloudformation:props": { - "name": "cdk.test.", - "type": "TXT", "hostedZoneId": { "Ref": "PublicZone2E1C4E34" }, + "name": "cdk.test.", "resourceRecords": [ "\"this is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long stringthis is a very long s\"\"tring\"" ], - "ttl": "1800" + "ttl": "1800", + "type": "TXT" } }, "constructInfo": { @@ -711,7 +898,7 @@ "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.270" + "version": "10.2.55" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.ts index 5cfebe170a350..8ff7c2c70a4f3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-route53/test/integ.route53.ts @@ -1,6 +1,6 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as cdk from 'aws-cdk-lib'; -import { ARecord, CaaAmazonRecord, CnameRecord, PrivateHostedZone, PublicHostedZone, RecordTarget, TxtRecord } from 'aws-cdk-lib/aws-route53'; +import { ARecord, CaaAmazonRecord, CnameRecord, Continent, GeoLocation, PrivateHostedZone, PublicHostedZone, RecordTarget, TxtRecord } from 'aws-cdk-lib/aws-route53'; const app = new cdk.App(); @@ -47,6 +47,40 @@ new ARecord(stack, 'A', { target: RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), }); +new ARecord(stack, 'GeoLocationContinent', { + zone: privateZone, + recordName: 'geolocation', + target: RecordTarget.fromIpAddresses('1.2.3.0', '5.6.7.0'), + geoLocation: GeoLocation.continent(Continent.EUROPE), +}); + +new ARecord(stack, 'GeoLocationCountry', { + zone: privateZone, + recordName: 'geolocation', + target: RecordTarget.fromIpAddresses('1.2.3.1', '5.6.7.1'), + geoLocation: GeoLocation.country('DE'), +}); + +new ARecord(stack, 'GeoLocationSubDividion', { + zone: privateZone, + recordName: 'geolocation', + target: RecordTarget.fromIpAddresses('1.2.3.2', '5.6.7.2'), + geoLocation: GeoLocation.subdivision('WA'), +}); + +new ARecord(stack, 'GeoLocationSubDividionUA', { + zone: privateZone, + target: RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.4'), + geoLocation: GeoLocation.subdivision('30', 'UA'), // Ukraine, Kyiv +}); + +new ARecord(stack, 'GeoLocationDefault', { + zone: privateZone, + recordName: 'geolocation', + target: RecordTarget.fromIpAddresses('1.2.3.3', '5.6.7.3'), + geoLocation: GeoLocation.default(), +}); + new CaaAmazonRecord(stack, 'CaaAmazon', { zone: publicZone, }); diff --git a/packages/aws-cdk-lib/aws-route53/README.md b/packages/aws-cdk-lib/aws-route53/README.md index 3521fe8bc7669..3b5ee894c9494 100644 --- a/packages/aws-cdk-lib/aws-route53/README.md +++ b/packages/aws-cdk-lib/aws-route53/README.md @@ -117,6 +117,40 @@ new route53.AaaaRecord(this, 'Alias', { }); ``` +[Geolocation routing](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy-geo.html) can be enabled for continent, country or subdivision: + +```ts +declare const myZone: route53.HostedZone; + +// continent +new route53.ARecord(this, 'ARecordGeoLocationContinent', { + zone: myZone, + target: route53.RecordTarget.fromIpAddresses('1.2.3.0', '5.6.7.0'), + geoLocation: route53.GeoLocation.continent(route53.Continent.EUROPE), +}); + +// country +new route53.ARecord(this, 'ARecordGeoLocationCountry', { + zone: myZone, + target: route53.RecordTarget.fromIpAddresses('1.2.3.1', '5.6.7.1'), + geoLocation: route53.GeoLocation.country('DE'), // Germany +}); + +// subdivision +new route53.ARecord(this, 'ARecordGeoLocationSubDividion', { + zone: myZone, + target: route53.RecordTarget.fromIpAddresses('1.2.3.2', '5.6.7.2'), + geoLocation: route53.GeoLocation.subdivision('WA'), // Washington +}); + +// default (wildcard record if no specific record is found) +new route53.ARecord(this, 'ARecordGeoLocationDefault', { + zone: myZone, + target: route53.RecordTarget.fromIpAddresses('1.2.3.3', '5.6.7.3'), + geoLocation: route53.GeoLocation.default(), +}); +``` + Constructs are available for A, AAAA, CAA, CNAME, MX, NS, SRV and TXT records. Use the `CaaAmazonRecord` construct to easily restrict certificate authorities diff --git a/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts b/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts new file mode 100644 index 0000000000000..8f6898ed83c59 --- /dev/null +++ b/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts @@ -0,0 +1,114 @@ +/** + * Routing based on geographical location. + */ +export class GeoLocation { + /** + * Geolocation resource record based on continent code. + * @param continentCode Continent. + * @returns Continent-based geolocation record + */ + public static continent(continentCode: Continent) { + return new GeoLocation(continentCode, undefined, undefined); + } + + /** + * Geolocation resource record based on country code. + * @param countryCode Two-letter, uppercase country code for the country. + * See ISO 3166-1-alpha-2 code on the *International Organization for Standardization* website + * @see https://docs.aws.amazon.com/Route53/latest/APIReference/API_GeoLocation.html#Route53-Type-GeoLocation-CountryCode + * @see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + * @returns Country-based geolocation record + */ + public static country(countryCode: string) { + return new GeoLocation(undefined, countryCode, undefined); + } + + /** + * Geolocation resource record based on subdivision code (e.g. state of the United States). + * @param subdivisionCode Code of the subdivision (e.g. state of the United States) + * @param countryCode Country code (ISO 3166-1-alpha-2) of this record, by default US (United States). + * @see https://pe.usps.com/text/pub28/28apb.htm + * @see https://docs.aws.amazon.com/Route53/latest/APIReference/API_GeoLocation.html#Route53-Type-GeoLocation-SubdivisionCode + */ + public static subdivision(subdivisionCode: string, countryCode: string = 'US') { + return new GeoLocation(undefined, countryCode, subdivisionCode); + } + + /** + * Default (wildcard) routing record if no specific geolocation record is found. + * @returns Wildcard routing record + */ + public static default() { + return new GeoLocation(undefined, '*', undefined); + } + + private static COUNTRY_REGEX = /(^[A-Z]{2}|^\*{1})$/; + private static COUNTRY_FOR_SUBDIVISION_REGEX = /\b(?:UA|US)\b/; + private static SUBDIVISION_REGEX = /^[A-Z0-9]{1,3}$/; + + private static validateCountry(country: string) { + if (!GeoLocation.COUNTRY_REGEX.test(country)) { + // eslint-disable-next-line max-len + throw new Error(`Invalid country format for country: ${country}, country should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); + } + } + + private static validateCountryForSubdivision(country: string) { + if (!GeoLocation.COUNTRY_FOR_SUBDIVISION_REGEX.test(country)) { + // eslint-disable-next-line max-len + throw new Error(`Invalid country for subdivisions geolocation: ${country}, only UA (Ukraine) and US (United states) are supported`); + } + } + + private static validateSubDivision(subDivision: string) { + if (!GeoLocation.SUBDIVISION_REGEX.test(subDivision)) { + // eslint-disable-next-line max-len + throw new Error(`Invalid subdivision format for subdivision: ${subDivision}, subdivision should be alphanumeric and between 1 and 3 characters`); + } + } + + private constructor(readonly continentCode: Continent | undefined, readonly countryCode: string | undefined, + readonly subdivisionCode: string | undefined) { + if (subdivisionCode && countryCode) { + GeoLocation.validateCountryForSubdivision(countryCode); + GeoLocation.validateSubDivision(subdivisionCode); + } + if (countryCode) { + GeoLocation.validateCountry(countryCode); + } + } +} + +/** + * Continents for geolocation routing. + */ +export enum Continent { + /** + * Africa + */ + AFRICA = 'AF', + /** + * Antarctica + */ + ANTARCTICA = 'AN', + /** + * Asia + */ + ASIA = 'AS', + /** + * Europe + */ + EUROPE = 'EU', + /** + * Oceania + */ + OCEANIA = 'OC', + /** + * North America + */ + NORTH_AMERICA = 'NA', + /** + * South America + */ + SOUTH_AMERICA = 'SA', +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-route53/lib/index.ts b/packages/aws-cdk-lib/aws-route53/lib/index.ts index c35c57025fc26..2a6128e50c2bd 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/index.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/index.ts @@ -4,6 +4,7 @@ export * from './hosted-zone-provider'; export * from './hosted-zone-ref'; export * from './record-set'; export * from './vpc-endpoint-service-domain-name'; +export * from './geo-location'; // AWS::Route53 CloudFormation Resources: export * from './route53.generated'; diff --git a/packages/aws-cdk-lib/aws-route53/lib/record-set.ts b/packages/aws-cdk-lib/aws-route53/lib/record-set.ts index 33d71a34accb8..6e591efe57663 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/record-set.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/record-set.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { Construct } from 'constructs'; import { IAliasRecordTarget } from './alias-record-target'; +import { GeoLocation } from './geo-location'; import { IHostedZone } from './hosted-zone-ref'; import { CfnRecordSet } from './route53.generated'; import { determineFullyQualifiedDomainName } from './util'; @@ -155,6 +156,11 @@ export interface RecordSetOptions { */ readonly zone: IHostedZone; + /** + * The geographical origin for this record to return DNS records based on the user's location. + */ + readonly geoLocation?: GeoLocation; + /** * The subdomain name for this record. This should be relative to the zone root name. * @@ -270,6 +276,12 @@ export class RecordSet extends Resource implements IRecordSet { aliasTarget: props.target.aliasTarget && props.target.aliasTarget.bind(this, props.zone), ttl, comment: props.comment, + geoLocation: props.geoLocation ? { + continentCode: props.geoLocation.continentCode, + countryCode: props.geoLocation.countryCode, + subdivisionCode: props.geoLocation.subdivisionCode, + } : undefined, + setIdentifier: props.geoLocation ? this.configureSetIdentifer(props.geoLocation) : undefined, }); this.domainName = recordSet.ref; @@ -317,6 +329,20 @@ export class RecordSet extends Resource implements IRecordSet { recordSet.node.addDependency(customResource); } } + + private configureSetIdentifer(props: GeoLocation): string | undefined { + let identifier = 'GEO'; + if (props.continentCode) { + identifier = identifier.concat('_CONTINENT_', props.continentCode); + } + if (props.countryCode) { + identifier = identifier.concat('_COUNTRY_', props.countryCode); + } + if (props.subdivisionCode) { + identifier = identifier.concat('_SUBDIVISION_', props.subdivisionCode); + } + return identifier; + } } /** diff --git a/packages/aws-cdk-lib/aws-route53/test/geo-location.test.ts b/packages/aws-cdk-lib/aws-route53/test/geo-location.test.ts new file mode 100644 index 0000000000000..c8eb4261b9957 --- /dev/null +++ b/packages/aws-cdk-lib/aws-route53/test/geo-location.test.ts @@ -0,0 +1,43 @@ +import * as route53 from '../lib'; + +describe('geo location', () => { + test('valid continent', () => { + expect(route53.GeoLocation.continent(route53.Continent.EUROPE).continentCode).toEqual('EU'); + }); + + test('invalid country', () => { + const error = /Invalid country format for country: .*/; + expect(() => { route53.GeoLocation.country('de'); }).toThrow(error); + expect(() => { route53.GeoLocation.country('us'); }).toThrow(error); + expect(() => { route53.GeoLocation.country('a'); }).toThrow(error); + expect(() => { route53.GeoLocation.country('abc'); }).toThrow(error); + expect(() => { route53.GeoLocation.country('01'); }).toThrow(error); + }); + + test('valid country', () => { + expect(route53.GeoLocation.country('EU').countryCode).toEqual('EU'); + expect(route53.GeoLocation.country('US').countryCode).toEqual('US'); + }); + + test('invalid subdivision', () => { + const error = /Invalid subdivision format for subdivision: .*/; + expect(() => { route53.GeoLocation.subdivision('aa'); }).toThrow(error); + expect(() => { route53.GeoLocation.subdivision('ABCD'); }).toThrow(error); + expect(() => { route53.GeoLocation.subdivision('abc'); }).toThrow(error); + expect(() => { route53.GeoLocation.subdivision('1234'); }).toThrow(error); + }); + + test('invalid country for subdivision', () => { + const error = /Invalid country for subdivisions geolocation: .*/; + expect(() => { route53.GeoLocation.subdivision('DE', 'DE'); }).toThrow(error); + }); + + test('valid subdivision', () => { + expect(route53.GeoLocation.subdivision('WA').subdivisionCode).toEqual('WA'); + expect(route53.GeoLocation.subdivision('05', 'UA').subdivisionCode).toEqual('05'); + }); + + test('default record', () => { + expect(route53.GeoLocation.default().countryCode).toEqual('*'); + }); +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-route53/test/record-set.test.ts b/packages/aws-cdk-lib/aws-route53/test/record-set.test.ts index 075b6eb2a81ce..13f27f7eb6282 100644 --- a/packages/aws-cdk-lib/aws-route53/test/record-set.test.ts +++ b/packages/aws-cdk-lib/aws-route53/test/record-set.test.ts @@ -579,6 +579,183 @@ describe('record set', () => { }); }); + test('A record, GeoLocation routing for continent', () => { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.ARecord(stack, 'A', { + zone, + recordName: 'www', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), + geoLocation: route53.GeoLocation.continent(route53.Continent.EUROPE), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'A', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '1.2.3.4', + '5.6.7.8', + ], + TTL: '1800', + GeoLocation: { + ContinentCode: 'EU', + }, + SetIdentifier: 'GEO_CONTINENT_EU', + }); + }); + + test('A record, GeoLocation routing for country', () => { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.ARecord(stack, 'A', { + zone, + recordName: 'www', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), + geoLocation: route53.GeoLocation.country('DE'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'A', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '1.2.3.4', + '5.6.7.8', + ], + TTL: '1800', + GeoLocation: { + CountryCode: 'DE', + }, + SetIdentifier: 'GEO_COUNTRY_DE', + }); + }); + + test('A record, GeoLocation routing for subdivision', () => { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.ARecord(stack, 'A', { + zone, + recordName: 'www', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), + geoLocation: route53.GeoLocation.subdivision('WA'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'A', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '1.2.3.4', + '5.6.7.8', + ], + TTL: '1800', + GeoLocation: { + CountryCode: 'US', + SubdivisionCode: 'WA', + }, + SetIdentifier: 'GEO_COUNTRY_US_SUBDIVISION_WA', + }); + }); + + test('A record, GeoLocation routing for subdivision with country attribute', () => { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.ARecord(stack, 'A', { + zone, + recordName: 'www', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), + geoLocation: route53.GeoLocation.subdivision('05', 'UA'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'A', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '1.2.3.4', + '5.6.7.8', + ], + TTL: '1800', + GeoLocation: { + CountryCode: 'UA', + SubdivisionCode: '05', + }, + SetIdentifier: 'GEO_COUNTRY_UA_SUBDIVISION_05', + }); + }); + + test('A record, GeoLocation default record', () => { + // GIVEN + const stack = new Stack(); + + const zone = new route53.HostedZone(stack, 'HostedZone', { + zoneName: 'myzone', + }); + + // WHEN + new route53.ARecord(stack, 'A', { + zone, + recordName: 'www', + target: route53.RecordTarget.fromIpAddresses('1.2.3.4', '5.6.7.8'), + geoLocation: route53.GeoLocation.default(), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::Route53::RecordSet', { + Name: 'www.myzone.', + Type: 'A', + HostedZoneId: { + Ref: 'HostedZoneDB99F866', + }, + ResourceRecords: [ + '1.2.3.4', + '5.6.7.8', + ], + TTL: '1800', + GeoLocation: { + CountryCode: '*', + }, + SetIdentifier: 'GEO_COUNTRY_*', + }); + }); + testDeprecated('Cross account zone delegation record with parentHostedZoneId', () => { // GIVEN const stack = new Stack();