Skip to content

Commit 3ea32ea

Browse files
authored
feat(custom-domains): adds support for HostedZoneName in Domain section of the Api (#1408)
1 parent 66e3d70 commit 3ea32ea

12 files changed

+851
-94
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Custom Domains support
2+
3+
Example SAM template for setting up Api Gateway resources for custom domains.
4+
5+
## Prerequisites for setting up custom domains
6+
1. A domain name. You can purchase a domain name from a domain name provider.
7+
1. A certificate ARN. Set up or import a valid certificate into AWS Certificate Manager. If the endpoint is EDGE, the certificate must be created in us-east-1.
8+
1. A HostedZone in Route53 for the domain name.
9+
10+
## PostRequisites
11+
After deploying the template, make sure you configure the DNS settings on the domain name provider's website. You will need to add Type A and Type AAAA DNS records that are point to ApiGateway's Hosted Zone Id. Read more [here](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-api-gateway.html)
12+
13+
## Running the example
14+
15+
```bash
16+
$ sam deploy \
17+
--template-file /path_to_template/packaged-template.yaml \
18+
--stack-name my-new-stack \
19+
--capabilities CAPABILITY_IAM
20+
```
21+
22+
Curl to the endpoint "http://example.com/home/fetch" should hit the Api.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
Parameters:
2+
DomainName:
3+
Type: String
4+
Default: 'example.com'
5+
ACMCertificateArn:
6+
Type: String
7+
Default: 'cert-arn-in-us-east-1'
8+
Resources:
9+
MyFunction:
10+
Type: AWS::Serverless::Function
11+
Properties:
12+
InlineCode: |
13+
exports.handler = async (event) => {
14+
const response = {
15+
statusCode: 200,
16+
body: JSON.stringify('Hello from Lambda!'),
17+
};
18+
return response;
19+
};
20+
Handler: index.handler
21+
Runtime: nodejs8.10
22+
Events:
23+
Fetch:
24+
Type: Api
25+
Properties:
26+
RestApiId: !Ref MyApi
27+
Method: Post
28+
Path: /fetch
29+
30+
MyApi:
31+
Type: AWS::Serverless::Api
32+
Properties:
33+
OpenApiVersion: 3.0.1
34+
StageName: Prod
35+
Domain:
36+
DomainName: !Ref DomainName
37+
CertificateArn: !Ref ACMCertificateArn
38+
EndpointConfiguration: EDGE
39+
BasePath:
40+
- /home
41+
Route53:
42+
HostedZoneName: www.my-domain.com.
43+
IpV6: true
44+
## ====== Everything below here is optional, leave this out if you want to use the internal Api Gateway distribution =======
45+
DistributionDomainName: !GetAtt Distribution.DomainName
46+
47+
Distribution:
48+
Type: AWS::CloudFront::Distribution
49+
Properties:
50+
DistributionConfig:
51+
Enabled: true
52+
HttpVersion: http2
53+
Origins:
54+
- DomainName: !Ref DomainName
55+
Id: !Ref DomainName
56+
CustomOriginConfig:
57+
HTTPPort: 80
58+
HTTPSPort: 443
59+
OriginProtocolPolicy: https-only
60+
DefaultCacheBehavior:
61+
AllowedMethods: [ HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH ]
62+
ForwardedValues:
63+
QueryString: false
64+
SmoothStreaming: false
65+
Compress: true
66+
TargetOriginId: !Ref DomainName
67+
ViewerProtocolPolicy: redirect-to-https
68+
PriceClass: PriceClass_100
69+
ViewerCertificate:
70+
SslSupportMethod: sni-only
71+
AcmCertificateArn: !Ref ACMCertificateArn

samtranslator/model/api/api_generator.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,21 @@ def _construct_api_domain(self, rest_api):
338338
record_set_group = None
339339
if self.domain.get("Route53") is not None:
340340
route53 = self.domain.get("Route53")
341-
if route53.get("HostedZoneId") is None:
341+
if route53.get("HostedZoneId") is None and route53.get("HostedZoneName") is None:
342342
raise InvalidResourceException(
343-
self.logical_id, "HostedZoneId is required to enable Route53 support on Custom Domains."
343+
self.logical_id,
344+
"HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.",
344345
)
345-
logical_id = logical_id_generator.LogicalIdGenerator("", route53.get("HostedZoneId")).gen()
346+
logical_id = logical_id_generator.LogicalIdGenerator(
347+
"", route53.get("HostedZoneId") or route53.get("HostedZoneName")
348+
).gen()
346349
record_set_group = Route53RecordSetGroup(
347350
"RecordSetGroup" + logical_id, attributes=self.passthrough_resource_attributes
348351
)
349-
record_set_group.HostedZoneId = route53.get("HostedZoneId")
352+
if "HostedZoneId" in route53:
353+
record_set_group.HostedZoneId = route53.get("HostedZoneId")
354+
if "HostedZoneName" in route53:
355+
record_set_group.HostedZoneName = route53.get("HostedZoneName")
350356
record_set_group.RecordSets = self._construct_record_sets_for_domain(self.domain)
351357

352358
return domain, basepath_resource_list, record_set_group

samtranslator/model/route53.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@
44

55
class Route53RecordSetGroup(Resource):
66
resource_type = "AWS::Route53::RecordSetGroup"
7-
property_types = {"HostedZoneId": PropertyType(False, is_str()), "RecordSets": PropertyType(False, is_type(list))}
7+
property_types = {
8+
"HostedZoneId": PropertyType(False, is_str()),
9+
"HostedZoneName": PropertyType(False, is_str()),
10+
"RecordSets": PropertyType(False, is_type(list)),
11+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Parameters:
2+
DomainName:
3+
Type: String
4+
Default: 'example.com'
5+
ACMCertificateArn:
6+
Type: String
7+
Default: 'cert-arn-in-us-east-1'
8+
Resources:
9+
MyFunction:
10+
Type: AWS::Serverless::Function
11+
Properties:
12+
InlineCode: |
13+
exports.handler = async (event) => {
14+
const response = {
15+
statusCode: 200,
16+
body: JSON.stringify('Hello from Lambda!'),
17+
};
18+
return response;
19+
};
20+
Handler: index.handler
21+
Runtime: nodejs12.x
22+
Events:
23+
Fetch:
24+
Type: Api
25+
Properties:
26+
RestApiId: !Ref MyApi
27+
Method: Post
28+
Path: /fetch
29+
30+
MyApi:
31+
Type: AWS::Serverless::Api
32+
Properties:
33+
OpenApiVersion: 3.0.1
34+
StageName: Prod
35+
Domain:
36+
DomainName: !Ref DomainName
37+
CertificateArn: !Ref ACMCertificateArn
38+
EndpointConfiguration: EDGE
39+
BasePath:
40+
- /one
41+
Route53:
42+
HostedZoneName: www.my-domain.com.
43+
IpV6: true
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
{
2+
"Parameters": {
3+
"ACMCertificateArn": {
4+
"Default": "cert-arn-in-us-east-1",
5+
"Type": "String"
6+
},
7+
"DomainName": {
8+
"Default": "example.com",
9+
"Type": "String"
10+
}
11+
},
12+
"Resources": {
13+
"MyFunction": {
14+
"Type": "AWS::Lambda::Function",
15+
"Properties": {
16+
"Handler": "index.handler",
17+
"Code": {
18+
"ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n"
19+
},
20+
"Role": {
21+
"Fn::GetAtt": [
22+
"MyFunctionRole",
23+
"Arn"
24+
]
25+
},
26+
"Runtime": "nodejs12.x",
27+
"Tags": [
28+
{
29+
"Value": "SAM",
30+
"Key": "lambda:createdBy"
31+
}
32+
]
33+
}
34+
},
35+
"MyFunctionFetchPermissionProd": {
36+
"Type": "AWS::Lambda::Permission",
37+
"Properties": {
38+
"Action": "lambda:InvokeFunction",
39+
"Principal": "apigateway.amazonaws.com",
40+
"FunctionName": {
41+
"Ref": "MyFunction"
42+
},
43+
"SourceArn": {
44+
"Fn::Sub": [
45+
"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch",
46+
{
47+
"__Stage__": "*",
48+
"__ApiId__": {
49+
"Ref": "MyApi"
50+
}
51+
}
52+
]
53+
}
54+
}
55+
},
56+
"ApiGatewayDomainName0caaf24ab1": {
57+
"Type": "AWS::ApiGateway::DomainName",
58+
"Properties": {
59+
"CertificateArn": "cert-arn-in-us-east-1",
60+
"EndpointConfiguration": {
61+
"Types": [
62+
"EDGE"
63+
]
64+
},
65+
"DomainName": "example.com"
66+
}
67+
},
68+
"MyApiProdStage": {
69+
"Type": "AWS::ApiGateway::Stage",
70+
"Properties": {
71+
"DeploymentId": {
72+
"Ref": "MyApiDeploymenteb58d7577a"
73+
},
74+
"RestApiId": {
75+
"Ref": "MyApi"
76+
},
77+
"StageName": "Prod"
78+
}
79+
},
80+
"MyApioneBasePathMapping": {
81+
"Type": "AWS::ApiGateway::BasePathMapping",
82+
"Properties": {
83+
"BasePath": "one",
84+
"DomainName": {
85+
"Ref": "ApiGatewayDomainName0caaf24ab1"
86+
},
87+
"RestApiId": {
88+
"Ref": "MyApi"
89+
},
90+
"Stage": {
91+
"Ref": "MyApiProdStage"
92+
}
93+
}
94+
},
95+
"MyApi": {
96+
"Type": "AWS::ApiGateway::RestApi",
97+
"Properties": {
98+
"Body": {
99+
"info": {
100+
"version": "1.0",
101+
"title": {
102+
"Ref": "AWS::StackName"
103+
}
104+
},
105+
"paths": {
106+
"/fetch": {
107+
"post": {
108+
"x-amazon-apigateway-integration": {
109+
"httpMethod": "POST",
110+
"type": "aws_proxy",
111+
"uri": {
112+
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
113+
}
114+
},
115+
"responses": {}
116+
}
117+
}
118+
},
119+
"openapi": "3.0.1"
120+
}
121+
}
122+
},
123+
"MyFunctionRole": {
124+
"Type": "AWS::IAM::Role",
125+
"Properties": {
126+
"AssumeRolePolicyDocument": {
127+
"Version": "2012-10-17",
128+
"Statement": [
129+
{
130+
"Action": [
131+
"sts:AssumeRole"
132+
],
133+
"Effect": "Allow",
134+
"Principal": {
135+
"Service": [
136+
"lambda.amazonaws.com"
137+
]
138+
}
139+
}
140+
]
141+
},
142+
"ManagedPolicyArns": [
143+
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
144+
],
145+
"Tags": [
146+
{
147+
"Value": "SAM",
148+
"Key": "lambda:createdBy"
149+
}
150+
]
151+
}
152+
},
153+
"RecordSetGroup456ebaf280": {
154+
"Type": "AWS::Route53::RecordSetGroup",
155+
"Properties": {
156+
"HostedZoneName": "www.my-domain.com.",
157+
"RecordSets": [
158+
{
159+
"AliasTarget": {
160+
"HostedZoneId": "Z2FDTNDATAQYW2",
161+
"DNSName": {
162+
"Fn::GetAtt": [
163+
"ApiGatewayDomainName0caaf24ab1",
164+
"DistributionDomainName"
165+
]
166+
}
167+
},
168+
"Type": "A",
169+
"Name": "example.com"
170+
},
171+
{
172+
"AliasTarget": {
173+
"HostedZoneId": "Z2FDTNDATAQYW2",
174+
"DNSName": {
175+
"Fn::GetAtt": [
176+
"ApiGatewayDomainName0caaf24ab1",
177+
"DistributionDomainName"
178+
]
179+
}
180+
},
181+
"Type": "AAAA",
182+
"Name": "example.com"
183+
}
184+
]
185+
}
186+
},
187+
"MyApiDeploymenteb58d7577a": {
188+
"Type": "AWS::ApiGateway::Deployment",
189+
"Properties": {
190+
"RestApiId": {
191+
"Ref": "MyApi"
192+
},
193+
"Description": "RestApi deployment id: eb58d7577a65af049c9c6f10c9d8b286de6b5aeb"
194+
}
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)