Skip to content

Commit

Permalink
feat(custom-domains): adds support for HostedZoneName in Domain secti…
Browse files Browse the repository at this point in the history
…on of the Api (#1408)
  • Loading branch information
mbarneyjr authored Jan 31, 2020
1 parent 66e3d70 commit 3ea32ea
Show file tree
Hide file tree
Showing 12 changed files with 851 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Custom Domains support

Example SAM template for setting up Api Gateway resources for custom domains.

## Prerequisites for setting up custom domains
1. A domain name. You can purchase a domain name from a domain name provider.
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.
1. A HostedZone in Route53 for the domain name.

## PostRequisites
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)

## Running the example

```bash
$ sam deploy \
--template-file /path_to_template/packaged-template.yaml \
--stack-name my-new-stack \
--capabilities CAPABILITY_IAM
```

Curl to the endpoint "http://example.com/home/fetch" should hit the Api.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Parameters:
DomainName:
Type: String
Default: 'example.com'
ACMCertificateArn:
Type: String
Default: 'cert-arn-in-us-east-1'
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Handler: index.handler
Runtime: nodejs8.10
Events:
Fetch:
Type: Api
Properties:
RestApiId: !Ref MyApi
Method: Post
Path: /fetch

MyApi:
Type: AWS::Serverless::Api
Properties:
OpenApiVersion: 3.0.1
StageName: Prod
Domain:
DomainName: !Ref DomainName
CertificateArn: !Ref ACMCertificateArn
EndpointConfiguration: EDGE
BasePath:
- /home
Route53:
HostedZoneName: www.my-domain.com.
IpV6: true
## ====== Everything below here is optional, leave this out if you want to use the internal Api Gateway distribution =======
DistributionDomainName: !GetAtt Distribution.DomainName

Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
HttpVersion: http2
Origins:
- DomainName: !Ref DomainName
Id: !Ref DomainName
CustomOriginConfig:
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: https-only
DefaultCacheBehavior:
AllowedMethods: [ HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH ]
ForwardedValues:
QueryString: false
SmoothStreaming: false
Compress: true
TargetOriginId: !Ref DomainName
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_100
ViewerCertificate:
SslSupportMethod: sni-only
AcmCertificateArn: !Ref ACMCertificateArn
14 changes: 10 additions & 4 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,15 +338,21 @@ def _construct_api_domain(self, rest_api):
record_set_group = None
if self.domain.get("Route53") is not None:
route53 = self.domain.get("Route53")
if route53.get("HostedZoneId") is None:
if route53.get("HostedZoneId") is None and route53.get("HostedZoneName") is None:
raise InvalidResourceException(
self.logical_id, "HostedZoneId is required to enable Route53 support on Custom Domains."
self.logical_id,
"HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.",
)
logical_id = logical_id_generator.LogicalIdGenerator("", route53.get("HostedZoneId")).gen()
logical_id = logical_id_generator.LogicalIdGenerator(
"", route53.get("HostedZoneId") or route53.get("HostedZoneName")
).gen()
record_set_group = Route53RecordSetGroup(
"RecordSetGroup" + logical_id, attributes=self.passthrough_resource_attributes
)
record_set_group.HostedZoneId = route53.get("HostedZoneId")
if "HostedZoneId" in route53:
record_set_group.HostedZoneId = route53.get("HostedZoneId")
if "HostedZoneName" in route53:
record_set_group.HostedZoneName = route53.get("HostedZoneName")
record_set_group.RecordSets = self._construct_record_sets_for_domain(self.domain)

return domain, basepath_resource_list, record_set_group
Expand Down
6 changes: 5 additions & 1 deletion samtranslator/model/route53.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@

class Route53RecordSetGroup(Resource):
resource_type = "AWS::Route53::RecordSetGroup"
property_types = {"HostedZoneId": PropertyType(False, is_str()), "RecordSets": PropertyType(False, is_type(list))}
property_types = {
"HostedZoneId": PropertyType(False, is_str()),
"HostedZoneName": PropertyType(False, is_str()),
"RecordSets": PropertyType(False, is_type(list)),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Parameters:
DomainName:
Type: String
Default: 'example.com'
ACMCertificateArn:
Type: String
Default: 'cert-arn-in-us-east-1'
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
InlineCode: |
exports.handler = async (event) => {
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
Handler: index.handler
Runtime: nodejs12.x
Events:
Fetch:
Type: Api
Properties:
RestApiId: !Ref MyApi
Method: Post
Path: /fetch

MyApi:
Type: AWS::Serverless::Api
Properties:
OpenApiVersion: 3.0.1
StageName: Prod
Domain:
DomainName: !Ref DomainName
CertificateArn: !Ref ACMCertificateArn
EndpointConfiguration: EDGE
BasePath:
- /one
Route53:
HostedZoneName: www.my-domain.com.
IpV6: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
{
"Parameters": {
"ACMCertificateArn": {
"Default": "cert-arn-in-us-east-1",
"Type": "String"
},
"DomainName": {
"Default": "example.com",
"Type": "String"
}
},
"Resources": {
"MyFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Handler": "index.handler",
"Code": {
"ZipFile": "exports.handler = async (event) => {\n const response = {\n statusCode: 200,\n body: JSON.stringify('Hello from Lambda!'),\n };\n return response;\n};\n"
},
"Role": {
"Fn::GetAtt": [
"MyFunctionRole",
"Arn"
]
},
"Runtime": "nodejs12.x",
"Tags": [
{
"Value": "SAM",
"Key": "lambda:createdBy"
}
]
}
},
"MyFunctionFetchPermissionProd": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"FunctionName": {
"Ref": "MyFunction"
},
"SourceArn": {
"Fn::Sub": [
"arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch",
{
"__Stage__": "*",
"__ApiId__": {
"Ref": "MyApi"
}
}
]
}
}
},
"ApiGatewayDomainName0caaf24ab1": {
"Type": "AWS::ApiGateway::DomainName",
"Properties": {
"CertificateArn": "cert-arn-in-us-east-1",
"EndpointConfiguration": {
"Types": [
"EDGE"
]
},
"DomainName": "example.com"
}
},
"MyApiProdStage": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {
"Ref": "MyApiDeploymenteb58d7577a"
},
"RestApiId": {
"Ref": "MyApi"
},
"StageName": "Prod"
}
},
"MyApioneBasePathMapping": {
"Type": "AWS::ApiGateway::BasePathMapping",
"Properties": {
"BasePath": "one",
"DomainName": {
"Ref": "ApiGatewayDomainName0caaf24ab1"
},
"RestApiId": {
"Ref": "MyApi"
},
"Stage": {
"Ref": "MyApiProdStage"
}
}
},
"MyApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Body": {
"info": {
"version": "1.0",
"title": {
"Ref": "AWS::StackName"
}
},
"paths": {
"/fetch": {
"post": {
"x-amazon-apigateway-integration": {
"httpMethod": "POST",
"type": "aws_proxy",
"uri": {
"Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations"
}
},
"responses": {}
}
}
},
"openapi": "3.0.1"
}
}
},
"MyFunctionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sts:AssumeRole"
],
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
],
"Tags": [
{
"Value": "SAM",
"Key": "lambda:createdBy"
}
]
}
},
"RecordSetGroup456ebaf280": {
"Type": "AWS::Route53::RecordSetGroup",
"Properties": {
"HostedZoneName": "www.my-domain.com.",
"RecordSets": [
{
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": {
"Fn::GetAtt": [
"ApiGatewayDomainName0caaf24ab1",
"DistributionDomainName"
]
}
},
"Type": "A",
"Name": "example.com"
},
{
"AliasTarget": {
"HostedZoneId": "Z2FDTNDATAQYW2",
"DNSName": {
"Fn::GetAtt": [
"ApiGatewayDomainName0caaf24ab1",
"DistributionDomainName"
]
}
},
"Type": "AAAA",
"Name": "example.com"
}
]
}
},
"MyApiDeploymenteb58d7577a": {
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "MyApi"
},
"Description": "RestApi deployment id: eb58d7577a65af049c9c6f10c9d8b286de6b5aeb"
}
}
}
}
Loading

0 comments on commit 3ea32ea

Please sign in to comment.