Skip to content
Merged
2 changes: 1 addition & 1 deletion bin/sam-translate.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def transform_template(input_file_path, output_file_path):
with open(output_file_path, "w") as f:
f.write(cloud_formation_template_prettified)

print ("Wrote transformed CloudFormation template to: " + output_file_path)
print("Wrote transformed CloudFormation template to: " + output_file_path)
except InvalidDocumentException as e:
errorMessage = reduce(lambda message, error: message + " " + error.message, e.causes, e.message)
LOG.error(errorMessage)
Expand Down
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)),
}
13 changes: 10 additions & 3 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,9 +799,16 @@ def to_cloudformation(self, **kwargs):
domain=self.Domain,
)

rest_api, deployment, stage, permissions, domain, basepath_mapping, route53, usage_plan_resources = api_generator.to_cloudformation(
redeploy_restapi_parameters
)
(
rest_api,
deployment,
stage,
permissions,
domain,
basepath_mapping,
route53,
usage_plan_resources,
) = api_generator.to_cloudformation(redeploy_restapi_parameters)

resources.extend([rest_api, deployment, stage])
resources.extend(permissions)
Expand Down
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
Expand Up @@ -104,7 +104,7 @@ def test_deployment_preference_with_codedeploy_predifined_configuration(self):
deployment_preference_collection.add(self.function_logical_id, {"Type": deployment_type})
deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id)

print (deployment_group.DeploymentConfigName)
print(deployment_group.DeploymentConfigName)
self.assertEqual(expected_deployment_config_name, deployment_group.DeploymentConfigName)

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down Expand Up @@ -133,7 +133,7 @@ def test_deployment_preference_with_conditional_custom_configuration(self):
deployment_preference_collection = DeploymentPreferenceCollection()
deployment_preference_collection.add(self.function_logical_id, {"Type": deployment_type})
deployment_group = deployment_preference_collection.deployment_group(self.function_logical_id)
print (deployment_group.DeploymentConfigName)
print(deployment_group.DeploymentConfigName)
self.assertEqual(expected_deployment_config_name, deployment_group.DeploymentConfigName)

@patch("boto3.session.Session.region_name", "ap-southeast-1")
Expand Down
Loading