diff --git a/examples/2016-10-31/custom_domains_with_route53/README.md b/examples/2016-10-31/custom_domains_with_route53/README.md new file mode 100644 index 0000000000..e4f6670b59 --- /dev/null +++ b/examples/2016-10-31/custom_domains_with_route53/README.md @@ -0,0 +1,21 @@ +# 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. +1. A Cloudfront Distribution for the domain if the endpoint is set to EDGE. + +## 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 +``` diff --git a/examples/2016-10-31/custom_domains_with_route53/template.yaml b/examples/2016-10-31/custom_domains_with_route53/template.yaml new file mode 100644 index 0000000000..80aaeebc7b --- /dev/null +++ b/examples/2016-10-31/custom_domains_with_route53/template.yaml @@ -0,0 +1,70 @@ +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: + - /fetch + Route53: + HostedZoneId: ZQ1UAL4EFZVME + IpV6: true + 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 \ No newline at end of file diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 4320a752f4..fd237a2c49 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -1,10 +1,11 @@ from collections import namedtuple from six import string_types -from samtranslator.model.intrinsics import ref +from samtranslator.model.intrinsics import ref, fnGetAtt from samtranslator.model.apigateway import (ApiGatewayDeployment, ApiGatewayRestApi, ApiGatewayStage, ApiGatewayAuthorizer, ApiGatewayResponse, ApiGatewayDomainName, ApiGatewayBasePathMapping) +from samtranslator.model.route53 import Route53RecordSetGroup from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration @@ -218,7 +219,7 @@ def _construct_api_domain(self, rest_api): Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: - return None, None + return None, None, None if self.domain.get('DomainName') is None or \ self.domain.get('CertificateArn') is None: @@ -226,15 +227,18 @@ def _construct_api_domain(self, rest_api): "Custom Domains only works if both DomainName and CertificateArn" " are provided") - logical_id = logical_id_generator.LogicalIdGenerator("", self.domain).gen() + self.domain['ApiDomainName'] = "{}{}".format('ApiGatewayDomainName', + logical_id_generator. + LogicalIdGenerator("", self.domain.get('DomainName')).gen()) - domain = ApiGatewayDomainName('ApiGatewayDomainName' + logical_id, + domain = ApiGatewayDomainName(self.domain.get('ApiDomainName'), attributes=self.passthrough_resource_attributes) domain.DomainName = self.domain.get('DomainName') endpoint = self.domain.get('EndpointConfiguration') if endpoint is None: endpoint = 'REGIONAL' + self.domain['EndpointConfiguration'] = 'REGIONAL' elif endpoint not in ['EDGE', 'REGIONAL']: raise InvalidResourceException(self.logical_id, "EndpointConfiguration for Custom Domains must be" @@ -276,7 +280,58 @@ def _construct_api_domain(self, rest_api): basepath_mapping.BasePath = path basepath_resource_list.extend([basepath_mapping]) - return domain, basepath_resource_list + # Create the Route53 RecordSetGroup resource + record_set_group = None + if self.domain.get('Route53') is not None: + route53 = self.domain.get('Route53') + if route53.get('HostedZoneId') is None: + raise InvalidResourceException(self.logical_id, + "HostedZoneId is required to enable Route53 support on Custom Domains.") + logical_id = logical_id_generator.LogicalIdGenerator("", route53.get('HostedZoneId')).gen() + record_set_group = Route53RecordSetGroup('RecordSetGroup' + logical_id, + attributes=self.passthrough_resource_attributes) + record_set_group.HostedZoneId = route53.get('HostedZoneId') + record_set_group.RecordSets = self._construct_record_sets_for_domain(self.domain) + + return domain, basepath_resource_list, record_set_group + + def _construct_record_sets_for_domain(self, domain): + recordset_list = [] + recordset = {} + route53 = domain.get('Route53') + + recordset['Name'] = domain.get('DomainName') + recordset['Type'] = 'A' + recordset['AliasTarget'] = self._construct_alias_target(self.domain) + recordset_list.extend([recordset]) + + recordset_ipv6 = {} + if route53.get('IpV6') is not None and route53.get('IpV6') is True: + recordset_ipv6['Name'] = domain.get('DomainName') + recordset_ipv6['Type'] = 'AAAA' + recordset_ipv6['AliasTarget'] = self._construct_alias_target(self.domain) + recordset_list.extend([recordset_ipv6]) + + return recordset_list + + def _construct_alias_target(self, domain): + alias_target = {} + route53 = domain.get('Route53') + target_health = route53.get('EvaluateTargetHealth') + + if target_health is not None: + alias_target['EvaluateTargetHealth'] = target_health + if domain.get('EndpointConfiguration') == 'REGIONAL': + alias_target['HostedZoneId'] = fnGetAtt(self.domain.get('ApiDomainName'), 'RegionalHostedZoneId') + alias_target['DNSName'] = fnGetAtt(self.domain.get('ApiDomainName'), 'RegionalDomainName') + else: + if route53.get('DistributionDomainName') is None: + raise InvalidResourceException(self.logical_id, + "Custom Domains support for EDGE requires the name " + "of a Distribution resource") + alias_target['HostedZoneId'] = 'Z2FDTNDATAQYW2' + alias_target['DNSName'] = route53.get('DistributionDomainName') + return alias_target def to_cloudformation(self): """Generates CloudFormation resources from a SAM API resource @@ -285,7 +340,7 @@ def to_cloudformation(self): :rtype: tuple """ rest_api = self._construct_rest_api() - domain, basepath_mapping = self._construct_api_domain(rest_api) + domain, basepath_mapping, route53 = self._construct_api_domain(rest_api) deployment = self._construct_deployment(rest_api) swagger = None @@ -297,7 +352,7 @@ def to_cloudformation(self): stage = self._construct_stage(deployment, swagger) permissions = self._construct_authorizer_lambda_permission() - return rest_api, deployment, stage, permissions, domain, basepath_mapping + return rest_api, deployment, stage, permissions, domain, basepath_mapping, route53 def _add_cors(self): """ diff --git a/samtranslator/model/route53.py b/samtranslator/model/route53.py new file mode 100644 index 0000000000..25fd264df4 --- /dev/null +++ b/samtranslator/model/route53.py @@ -0,0 +1,10 @@ +from samtranslator.model import PropertyType, Resource +from samtranslator.model.types import is_type, is_str + + +class Route53RecordSetGroup(Resource): + resource_type = 'AWS::Route53::RecordSetGroup' + property_types = { + 'HostedZoneId': PropertyType(False, is_str()), + 'RecordSets': PropertyType(False, is_type(list)), + } diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index aa583efb88..79423b5396 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -10,7 +10,7 @@ from .tags.resource_tagging import get_tag_list from samtranslator.model import (PropertyType, SamResourceMacro, ResourceTypeResolver) -from samtranslator.model.apigateway import ApiGatewayDeployment, ApiGatewayStage +from samtranslator.model.apigateway import ApiGatewayDeployment, ApiGatewayStage, ApiGatewayDomainName from samtranslator.model.cloudformation import NestedStack from samtranslator.model.dynamodb import DynamoDBTable from samtranslator.model.exceptions import (InvalidEventException, @@ -489,6 +489,7 @@ class SamApi(SamResourceMacro): referable_properties = { "Stage": ApiGatewayStage.resource_type, "Deployment": ApiGatewayDeployment.resource_type, + "DomainName": ApiGatewayDomainName.resource_type } def to_cloudformation(self, **kwargs): @@ -531,7 +532,7 @@ def to_cloudformation(self, **kwargs): models=self.Models, domain=self.Domain) - rest_api, deployment, stage, permissions, domain, basepath_mapping = api_generator.to_cloudformation() + rest_api, deployment, stage, permissions, domain, basepath_mapping, route53 = api_generator.to_cloudformation() resources.extend([rest_api, deployment, stage]) resources.extend(permissions) @@ -539,6 +540,8 @@ def to_cloudformation(self, **kwargs): resources.extend([domain]) if basepath_mapping: resources.extend(basepath_mapping) + if route53: + resources.extend([route53]) return resources diff --git a/tests/translator/input/api_with_custom_domain_route53.yaml b/tests/translator/input/api_with_custom_domain_route53.yaml new file mode 100644 index 0000000000..80aaeebc7b --- /dev/null +++ b/tests/translator/input/api_with_custom_domain_route53.yaml @@ -0,0 +1,70 @@ +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: + - /fetch + Route53: + HostedZoneId: ZQ1UAL4EFZVME + IpV6: true + 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 \ No newline at end of file diff --git a/tests/translator/input/error_api_with_custom_domains_route53_invalid.yaml b/tests/translator/input/error_api_with_custom_domains_route53_invalid.yaml new file mode 100644 index 0000000000..647576aa3a --- /dev/null +++ b/tests/translator/input/error_api_with_custom_domains_route53_invalid.yaml @@ -0,0 +1,40 @@ +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: + Api: + Type: Api + Properties: + RestApiId: !Ref MyApi + Method: Put + Path: /get + 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: 'api-example.com' + CertificateArn: 'my-api-cert-arn' + EndpointConfiguration: 'EDGE' + BasePath: [ "/get", "/fetch" ] + Route53: + EvaluateTargetHealth: false diff --git a/tests/translator/output/api_with_basic_custom_domain.json b/tests/translator/output/api_with_basic_custom_domain.json index aaea82eea6..708cfa50de 100644 --- a/tests/translator/output/api_with_basic_custom_domain.json +++ b/tests/translator/output/api_with_basic_custom_domain.json @@ -53,17 +53,16 @@ } } }, - "MyApifetchBasePathMapping": { - "Type": "AWS::ApiGateway::BasePathMapping", + "ApiGatewayDomainName23cdccdf9c": { + "Type": "AWS::ApiGateway::DomainName", "Properties": { - "BasePath": "fetch", - "DomainName": "api-example.com", - "RestApiId": { - "Ref": "MyApi" + "CertificateArn": "my-api-cert-arn", + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] }, - "Stage": { - "Ref": "MyApiProdStage" - } + "DomainName": "api-example.com" } }, "MyFunctionImplicitGetPermissionProd": { @@ -91,7 +90,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyAnotherApiDeployment298b716713" + "Ref": "MyAnotherApiDeploymente89573907a" }, "RestApiId": { "Ref": "MyAnotherApi" @@ -99,15 +98,6 @@ "StageName": "Prod" } }, - "MyAnotherApiDeployment298b716713": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyAnotherApi" - }, - "Description": "RestApi deployment id: 298b71671363fe1258648c4e2231d5efb81ee2cb" - } - }, "MyFunctionFetchPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -129,6 +119,18 @@ } } }, + "MyAnotherApiBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", + "Properties": { + "DomainName": "another-example.com", + "RestApiId": { + "Ref": "MyAnotherApi" + }, + "Stage": { + "Ref": "MyAnotherApiProdStage" + } + } + }, "MyAnotherApi": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -157,26 +159,13 @@ } } }, - "ApiGatewayDomainName461285836f": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "CertificateArn": "my-api-cert-arn", - "EndpointConfiguration": { - "Types": [ - "EDGE" - ] - }, - "DomainName": "api-example.com" - } - }, - "ServerlessRestApiDeployment6f245e96ee": { + "MyApiDeploymentddf2dae73e": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { - "Ref": "ServerlessRestApi" + "Ref": "MyApi" }, - "Description": "RestApi deployment id: 6f245e96ee393dcf04d340172a2d8f590cff7bff", - "StageName": "Stage" + "Description": "RestApi deployment id: ddf2dae73e4e91595e8de4be7a30a1d207f32aab" } }, "MyFunctionRole": { @@ -256,32 +245,45 @@ } } }, - "MyAnotherApiBasePathMapping": { - "Type": "AWS::ApiGateway::BasePathMapping", + "MyAnotherApiDeploymente89573907a": { + "Type": "AWS::ApiGateway::Deployment", "Properties": { - "DomainName": "another-example.com", "RestApiId": { "Ref": "MyAnotherApi" }, - "Stage": { - "Ref": "MyAnotherApiProdStage" - } + "Description": "RestApi deployment id: e89573907aa0c18379ba3bcc497b31454e88a664" } }, - "MyApiDeployment345fbac8f2": { - "Type": "AWS::ApiGateway::Deployment", + "ApiGatewayDomainNameeab65c1531": { + "Type": "AWS::ApiGateway::DomainName", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "RegionalCertificateArn": "another-api-arn", + "DomainName": "another-example.com" + } + }, + "MyApifetchBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { + "BasePath": "fetch", + "DomainName": "api-example.com", "RestApiId": { "Ref": "MyApi" }, - "Description": "RestApi deployment id: 345fbac8f2a7592c5592018ad06db6de00590d35" + "Stage": { + "Ref": "MyApiProdStage" + } } }, "ServerlessRestApiProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeployment6f245e96ee" + "Ref": "ServerlessRestApiDeployment7db01b740c" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -289,6 +291,16 @@ "StageName": "Prod" } }, + "ServerlessRestApiDeployment7db01b740c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "ServerlessRestApi" + }, + "Description": "RestApi deployment id: 7db01b740cb897c4fc4175031f25e8123cc57ca9", + "StageName": "Stage" + } + }, "ServerlessRestApiBasePathMapping": { "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { @@ -305,7 +317,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment345fbac8f2" + "Ref": "MyApiDeploymentddf2dae73e" }, "RestApiId": { "Ref": "MyApi" @@ -341,18 +353,6 @@ } } }, - "ApiGatewayDomainName7a5525b8a3": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "RegionalCertificateArn": "another-api-arn", - "DomainName": "another-example.com" - } - }, "MyFunctionApiPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { diff --git a/tests/translator/output/api_with_basic_custom_domain_intrinsics.json b/tests/translator/output/api_with_basic_custom_domain_intrinsics.json index a41bfa93c5..4b7879e7d7 100644 --- a/tests/translator/output/api_with_basic_custom_domain_intrinsics.json +++ b/tests/translator/output/api_with_basic_custom_domain_intrinsics.json @@ -63,19 +63,28 @@ }, "Condition": "C1" }, - "MyApigetBasePathMapping": { - "Type": "AWS::ApiGateway::BasePathMapping", + "ApiGatewayDomainNamec0ed6fae34": { + "Type": "AWS::ApiGateway::DomainName", "Properties": { - "BasePath": "get", + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "RegionalCertificateArn": "another-api-arn", "DomainName": { "Fn::Sub": "example-ap-southeast-1.com" - }, + } + }, + "Condition": "C1" + }, + "MyApiDeployment6e1ee39b06": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { "RestApiId": { "Ref": "MyApi" }, - "Stage": { - "Ref": "MyApiProdStage" - } + "Description": "RestApi deployment id: 6e1ee39b068c65688d05f859c3952e41396ecede" }, "Condition": "C1" }, @@ -133,17 +142,18 @@ }, "Condition": "C1" }, - "ApiGatewayDomainName2bd847e760": { - "Type": "AWS::ApiGateway::DomainName", + "MyApigetBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "RegionalCertificateArn": "another-api-arn", + "BasePath": "get", "DomainName": { "Fn::Sub": "example-ap-southeast-1.com" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiProdStage" } }, "Condition": "C1" @@ -212,21 +222,11 @@ }, "Condition": "C1" }, - "MyApiDeploymentb73cd6313c": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApi" - }, - "Description": "RestApi deployment id: b73cd6313c957bca233f1137e39ffbe99e448848" - }, - "Condition": "C1" - }, "MyApiProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiDeploymentb73cd6313c" + "Ref": "MyApiDeployment6e1ee39b06" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/api_with_custom_domain_route53.json b/tests/translator/output/api_with_custom_domain_route53.json new file mode 100644 index 0000000000..fca043de36 --- /dev/null +++ b/tests/translator/output/api_with_custom_domain_route53.json @@ -0,0 +1,240 @@ +{ + "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": "nodejs8.10", + "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": "MyApiDeployment967cf1a6ff" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "MyApifetchBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", + "Properties": { + "BasePath": "fetch", + "DomainName": "example.com", + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "Distribution": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Origins": [ + { + "DomainName": { + "Ref": "DomainName" + }, + "Id": { + "Ref": "DomainName" + }, + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "HTTPPort": 80, + "HTTPSPort": 443 + } + } + ], + "PriceClass": "PriceClass_100", + "Enabled": true, + "DefaultCacheBehavior": { + "Compress": true, + "TargetOriginId": { + "Ref": "DomainName" + }, + "ViewerProtocolPolicy": "redirect-to-https", + "ForwardedValues": { + "QueryString": false + }, + "SmoothStreaming": false, + "AllowedMethods": [ + "HEAD", + "DELETE", + "POST", + "GET", + "OPTIONS", + "PUT", + "PATCH" + ] + }, + "ViewerCertificate": { + "SslSupportMethod": "sni-only", + "AcmCertificateArn": { + "Ref": "ACMCertificateArn" + } + }, + "HttpVersion": "http2" + } + } + }, + "MyApiDeployment967cf1a6ff": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 967cf1a6ff8e58a6e739bf5b7b59a7d658e01a40" + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "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" + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_basic_custom_domain.json b/tests/translator/output/aws-cn/api_with_basic_custom_domain.json index 97a2aae5d8..889f4512a6 100644 --- a/tests/translator/output/aws-cn/api_with_basic_custom_domain.json +++ b/tests/translator/output/aws-cn/api_with_basic_custom_domain.json @@ -53,6 +53,27 @@ } } }, + "MyApiDeploymentf93c9d77b0": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: f93c9d77b03a49d41597a99e8aaecf51eb585698" + } + }, + "ApiGatewayDomainName23cdccdf9c": { + "Type": "AWS::ApiGateway::DomainName", + "Properties": { + "CertificateArn": "my-api-cert-arn", + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "DomainName": "api-example.com" + } + }, "MyFunctionImplicitGetPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -78,7 +99,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyAnotherApiDeploymentc61fb61d5c" + "Ref": "MyAnotherApiDeployment78a7819e1c" }, "RestApiId": { "Ref": "MyAnotherApi" @@ -143,18 +164,6 @@ } } }, - "ApiGatewayDomainName461285836f": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "CertificateArn": "my-api-cert-arn", - "EndpointConfiguration": { - "Types": [ - "EDGE" - ] - }, - "DomainName": "api-example.com" - } - }, "MyApifetchBasePathMapping": { "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { @@ -265,30 +274,42 @@ } } }, - "MyApiDeploymentaf6c57ee88": { - "Type": "AWS::ApiGateway::Deployment", + "ApiGatewayDomainNameeab65c1531": { + "Type": "AWS::ApiGateway::DomainName", "Properties": { - "RestApiId": { - "Ref": "MyApi" + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] }, - "Description": "RestApi deployment id: af6c57ee88bdbf0d8ace828d5ac9cc02dba55922" + "RegionalCertificateArn": "another-api-arn", + "DomainName": "another-example.com" } }, - "ServerlessRestApiDeploymentd099245fb5": { + "ServerlessRestApiDeployment06e2333f46": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: d099245fb560a6117ebb37d418da8821c19f1eac", + "Description": "RestApi deployment id: 06e2333f46f9e8ce75b6d105e0fb5b1b7fed3aca", "StageName": "Stage" } }, + "MyAnotherApiDeployment78a7819e1c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyAnotherApi" + }, + "Description": "RestApi deployment id: 78a7819e1cd42aa753a8dfc29d0026d44aaf6335" + } + }, "ServerlessRestApiProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeploymentd099245fb5" + "Ref": "ServerlessRestApiDeployment06e2333f46" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -296,15 +317,6 @@ "StageName": "Prod" } }, - "MyAnotherApiDeploymentc61fb61d5c": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyAnotherApi" - }, - "Description": "RestApi deployment id: c61fb61d5cfc17e7e2f188dc18c5b11745bb6bc8" - } - }, "ServerlessRestApiBasePathMapping": { "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { @@ -321,7 +333,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiDeploymentaf6c57ee88" + "Ref": "MyApiDeploymentf93c9d77b0" }, "RestApiId": { "Ref": "MyApi" @@ -365,18 +377,6 @@ } } }, - "ApiGatewayDomainName7a5525b8a3": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "RegionalCertificateArn": "another-api-arn", - "DomainName": "another-example.com" - } - }, "MyFunctionApiPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { diff --git a/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics.json b/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics.json index c4a2c9abc6..f3efb64927 100644 --- a/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics.json +++ b/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics.json @@ -92,6 +92,16 @@ }, "Condition": "C1" }, + "MyApiDeploymentf260697bb0": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: f260697bb02eda112f7c58d850cf875f82e745a4" + }, + "Condition": "C1" + }, "ServerlessRestApiDeployment599c0b434d": { "Type": "AWS::ApiGateway::Deployment", "Properties": { @@ -205,17 +215,20 @@ }, "Condition": "C1" }, - "MyApiDeployment6cfbc38395": { - "Type": "AWS::ApiGateway::Deployment", + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentf260697bb0" + }, "RestApiId": { "Ref": "MyApi" }, - "Description": "RestApi deployment id: 6cfbc383958724f1d2d4d05d6e76f8670e66831f" + "StageName": "Prod" }, "Condition": "C1" }, - "ApiGatewayDomainName4bfafc5803": { + "ApiGatewayDomainNamec0cd2d9dfc": { "Type": "AWS::ApiGateway::DomainName", "Properties": { "EndpointConfiguration": { @@ -230,19 +243,6 @@ }, "Condition": "C1" }, - "MyApiProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiDeployment6cfbc38395" - }, - "RestApiId": { - "Ref": "MyApi" - }, - "StageName": "Prod" - }, - "Condition": "C1" - }, "ServerlessRestApi": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -327,4 +327,4 @@ "Condition": "C1" } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_custom_domain_route53.json b/tests/translator/output/aws-cn/api_with_custom_domain_route53.json new file mode 100644 index 0000000000..30113d4e37 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_custom_domain_route53.json @@ -0,0 +1,248 @@ +{ + "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": "nodejs8.10", + "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-cn: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" + } + }, + "MyApifetchBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", + "Properties": { + "BasePath": "fetch", + "DomainName": "example.com", + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "MyApiDeployment22e6f8d813": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 22e6f8d8130f68b2813a6e31d74b0ecccbf7820d" + } + }, + "Distribution": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Origins": [ + { + "DomainName": { + "Ref": "DomainName" + }, + "Id": { + "Ref": "DomainName" + }, + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "HTTPPort": 80, + "HTTPSPort": 443 + } + } + ], + "PriceClass": "PriceClass_100", + "Enabled": true, + "DefaultCacheBehavior": { + "Compress": true, + "TargetOriginId": { + "Ref": "DomainName" + }, + "ViewerProtocolPolicy": "redirect-to-https", + "ForwardedValues": { + "QueryString": false + }, + "SmoothStreaming": false, + "AllowedMethods": [ + "HEAD", + "DELETE", + "POST", + "GET", + "OPTIONS", + "PUT", + "PATCH" + ] + }, + "ViewerCertificate": { + "SslSupportMethod": "sni-only", + "AcmCertificateArn": { + "Ref": "ACMCertificateArn" + } + }, + "HttpVersion": "http2" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment22e6f8d813" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "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-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "openapi": "3.0.1" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain.json b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain.json index 23d1f3ff40..2ba58f4ad5 100644 --- a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain.json +++ b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain.json @@ -53,6 +53,18 @@ } } }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentf3135fd1a7" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, "MyFunctionImplicitGetPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -78,7 +90,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyAnotherApiDeployment619d82c169" + "Ref": "MyAnotherApiDeployment836d746e1a" }, "RestApiId": { "Ref": "MyAnotherApi" @@ -143,18 +155,6 @@ } } }, - "ApiGatewayDomainName461285836f": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "CertificateArn": "my-api-cert-arn", - "EndpointConfiguration": { - "Types": [ - "EDGE" - ] - }, - "DomainName": "api-example.com" - } - }, "MyApifetchBasePathMapping": { "Type": "AWS::ApiGateway::BasePathMapping", "Properties": { @@ -205,7 +205,7 @@ } } }, - "MyApi": { + "ServerlessRestApi": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { @@ -216,19 +216,7 @@ } }, "paths": { - "/get": { - "put": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" - } - }, - "responses": {} - } - }, - "/fetch": { + "/implicit": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -241,7 +229,7 @@ } } }, - "openapi": "3.0.1" + "swagger": "2.0" }, "EndpointConfiguration": { "Types": [ @@ -265,54 +253,45 @@ } } }, - "MyApiDeploymentb70cda6c19": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "RestApiId": { - "Ref": "MyApi" - }, - "Description": "RestApi deployment id: b70cda6c195d5d7d6300422874b03fdf3ac8cf16" - } - }, - "ServerlessRestApiProdStage": { - "Type": "AWS::ApiGateway::Stage", + "ApiGatewayDomainNameeab65c1531": { + "Type": "AWS::ApiGateway::DomainName", "Properties": { - "DeploymentId": { - "Ref": "ServerlessRestApiDeployment15c5513463" - }, - "RestApiId": { - "Ref": "ServerlessRestApi" + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] }, - "StageName": "Prod" + "RegionalCertificateArn": "another-api-arn", + "DomainName": "another-example.com" } }, - "ServerlessRestApiDeployment15c5513463": { + "ServerlessRestApiDeployment850347783d": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: 15c5513463f336ce7d80f9107134351289f4dc75", + "Description": "RestApi deployment id: 850347783d64d7c9e7f9528c3e117f7b29ef8bd9", "StageName": "Stage" } }, - "MyAnotherApiDeployment619d82c169": { + "MyApiDeploymentf3135fd1a7": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { - "Ref": "MyAnotherApi" + "Ref": "MyApi" }, - "Description": "RestApi deployment id: 619d82c169072c33e3a62ae66f9bbe723d9f6d71" + "Description": "RestApi deployment id: f3135fd1a745bd03eb49425618a88b1a2ca1c06e" } }, - "MyApiProdStage": { + "ServerlessRestApiProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiDeploymentb70cda6c19" + "Ref": "ServerlessRestApiDeployment850347783d" }, "RestApiId": { - "Ref": "MyApi" + "Ref": "ServerlessRestApi" }, "StageName": "Prod" } @@ -329,7 +308,19 @@ } } }, - "ServerlessRestApi": { + "ApiGatewayDomainName23cdccdf9c": { + "Type": "AWS::ApiGateway::DomainName", + "Properties": { + "CertificateArn": "my-api-cert-arn", + "EndpointConfiguration": { + "Types": [ + "EDGE" + ] + }, + "DomainName": "api-example.com" + } + }, + "MyApi": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { @@ -340,7 +331,19 @@ } }, "paths": { - "/implicit": { + "/get": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "responses": {} + } + }, + "/fetch": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -353,7 +356,7 @@ } } }, - "swagger": "2.0" + "openapi": "3.0.1" }, "EndpointConfiguration": { "Types": [ @@ -365,16 +368,13 @@ } } }, - "ApiGatewayDomainName7a5525b8a3": { - "Type": "AWS::ApiGateway::DomainName", + "MyAnotherApiDeployment836d746e1a": { + "Type": "AWS::ApiGateway::Deployment", "Properties": { - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] + "RestApiId": { + "Ref": "MyAnotherApi" }, - "RegionalCertificateArn": "another-api-arn", - "DomainName": "another-example.com" + "Description": "RestApi deployment id: 836d746e1af20f1dcc2c03b81a12a53828bf22e4" } }, "MyFunctionApiPermissionProd": { diff --git a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics.json b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics.json index 1b738ec6ef..217a892907 100644 --- a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics.json +++ b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics.json @@ -63,13 +63,13 @@ }, "Condition": "C1" }, - "MyApiDeploymentc15ba09b92": { + "MyApiDeployment49388946f5": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "MyApi" }, - "Description": "RestApi deployment id: c15ba09b92a98c578c133496aa7d330c1666c2b8" + "Description": "RestApi deployment id: 49388946f5e0e030c3139bb3f1b984fba94bbc84" }, "Condition": "C1" }, @@ -154,6 +154,21 @@ }, "Condition": "C1" }, + "ApiGatewayDomainName9c93aac102": { + "Type": "AWS::ApiGateway::DomainName", + "Properties": { + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "RegionalCertificateArn": "another-api-arn", + "DomainName": { + "Fn::Sub": "example-us-gov-west-1.com" + } + }, + "Condition": "C1" + }, "MyApi": { "Type": "AWS::ApiGateway::RestApi", "Properties": { @@ -215,26 +230,11 @@ }, "Condition": "C1" }, - "ApiGatewayDomainNamedf402213e0": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "EndpointConfiguration": { - "Types": [ - "REGIONAL" - ] - }, - "RegionalCertificateArn": "another-api-arn", - "DomainName": { - "Fn::Sub": "example-us-gov-west-1.com" - } - }, - "Condition": "C1" - }, "MyApiProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiDeploymentc15ba09b92" + "Ref": "MyApiDeployment49388946f5" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domain_route53.json b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53.json new file mode 100644 index 0000000000..860a1a2bf3 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53.json @@ -0,0 +1,248 @@ +{ + "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": "nodejs8.10", + "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-us-gov: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" + } + }, + "MyApifetchBasePathMapping": { + "Type": "AWS::ApiGateway::BasePathMapping", + "Properties": { + "BasePath": "fetch", + "DomainName": "example.com", + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": "Z2FDTNDATAQYW2", + "DNSName": { + "Fn::GetAtt": [ + "Distribution", + "DomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "MyApiDeploymentf3b0901355": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: f3b0901355f3d895ddaa0964df0aef6171c1d400" + } + }, + "Distribution": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "Origins": [ + { + "DomainName": { + "Ref": "DomainName" + }, + "Id": { + "Ref": "DomainName" + }, + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only", + "HTTPPort": 80, + "HTTPSPort": 443 + } + } + ], + "PriceClass": "PriceClass_100", + "Enabled": true, + "DefaultCacheBehavior": { + "Compress": true, + "TargetOriginId": { + "Ref": "DomainName" + }, + "ViewerProtocolPolicy": "redirect-to-https", + "ForwardedValues": { + "QueryString": false + }, + "SmoothStreaming": false, + "AllowedMethods": [ + "HEAD", + "DELETE", + "POST", + "GET", + "OPTIONS", + "PUT", + "PATCH" + ] + }, + "ViewerCertificate": { + "SslSupportMethod": "sni-only", + "AcmCertificateArn": { + "Ref": "ACMCertificateArn" + } + }, + "HttpVersion": "http2" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentf3b0901355" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "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-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "openapi": "3.0.1" + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_api_with_custom_domains_route53_invalid.json b/tests/translator/output/error_api_with_custom_domains_route53_invalid.json new file mode 100644 index 0000000000..5f21a0ab82 --- /dev/null +++ b/tests/translator/output/error_api_with_custom_domains_route53_invalid.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [MyApi] is invalid. HostedZoneId is required to enable Route53 support on Custom Domains." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. HostedZoneId is required to enable Route53 support on Custom Domains." +} diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 9b0a5363fc..6f91b77708 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -316,6 +316,7 @@ def test_transform_success(self, testcase, partition_with_region): 'api_with_apikey_required_openapi_3', 'api_with_basic_custom_domain', 'api_with_basic_custom_domain_intrinsics', + 'api_with_custom_domain_route53' ], [ ("aws", "ap-southeast-1"), @@ -545,7 +546,8 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'error_function_with_invalid_condition_name', 'error_invalid_document_empty_semantic_version', 'error_api_with_invalid_open_api_version_type', - 'error_api_with_custom_domains_invalid' + 'error_api_with_custom_domains_invalid', + 'error_api_with_custom_domains_route53_invalid' ]) @patch('boto3.session.Session.region_name', 'ap-southeast-1') @patch('samtranslator.plugins.application.serverless_app_plugin.ServerlessAppPlugin._sar_service_call', mock_sar_service_call) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 7b204bc197..03c5067d68 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -996,7 +996,13 @@ Enable custom domains to be configured with your Api. Currently only supports Cr ```yaml Domain: - DomainName: "example.com" # REQUIRED - CertificateARN: "" # REQUIRED | Must be a valid certificate ARN, and for EDGE endpoint configuration the certificate must be in us-east-1 + DomainName: String # REQUIRED | custom domain name being configured on the api, "www.example.com" + CertificateARN: String # REQUIRED | Must be a valid [certificate ARN](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-certificate.html), and for EDGE endpoint configuration the certificate must be in us-east-1 EndpointConfiguration: "EDGE" # optional | Default value is REGIONAL | Accepted values are EDGE | REGIONAL - BasePath: ["/foo"] # optional | Default value is '/' | List of basepaths to be configured with the ApiGateway Domain Name \ No newline at end of file + BasePath: + - String # optional | Default value is '/' | List of basepaths to be configured with the ApiGateway Domain Name + Route53: # optional | Default behavior is to treat as None - does not create Route53 resources | Enable these settings to create Route53 Recordsets + HostedZoneId: String # REQUIRED | Must be a hostedzoneid value of a [`AWS::Route53::HostedZone`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html) resource + EvaluateTargetHealth: Boolean # optional | default value is false + DistributionDomainName: String # REQUIRED IF the EndpointConfiguration is EDGE | Domain name of a [cloudfront distribution](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html) is required to create Route53 recordsets +``` \ No newline at end of file