diff --git a/DEVELOPMENT_GUIDE.rst b/DEVELOPMENT_GUIDE.rst index 3aa43366fc..ccd3f41061 100755 --- a/DEVELOPMENT_GUIDE.rst +++ b/DEVELOPMENT_GUIDE.rst @@ -30,15 +30,15 @@ Setup Python locally using `pyenv`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Black ~~~~~~~~ -We format our code using [Black](https://github.com/python/black) and verify the source code is black compliant -in Appveyor during PRs. You can find installation instructions on [Black's docs](https://black.readthedocs.io/en/stable/installation_and_usage.html). +We format our code using `Black`_ and verify the source code is black compliant +in Appveyor during PRs. You can find installation instructions on `Black's docs`_. After installing, you can check your formatting through our Makefile by running `make black-check`. To automatically update your code to match our formatting, please run `make black`. You can also integrate Black directly in your favorite IDE (instructions -can be found [here](https://black.readthedocs.io/en/stable/editor_integration.html)) +can be found `here`_) Pre-commit ~~~~~~~~~~ -If you don't wish to manually run black on each pr or install black manually, we have integrated black into git hooks through [pre-commit](https://pre-commit.com/). +If you don't wish to manually run black on each pr or install black manually, we have integrated black into git hooks through `pre-commit`_. After installing pre-commit, run `pre-commit install` in the root of the project. This will install black for you and run the black formatting on commit. @@ -85,6 +85,10 @@ Tests are also a documentation of the success and failure cases, which is crucia .. _pyenv: https://github.com/pyenv/pyenv .. _tox: http://tox.readthedocs.io/en/latest/ .. _installation instructions: https://github.com/pyenv/pyenv#installation +.. _Black: https://github.com/python/black +.. _Black's docs: https://black.readthedocs.io/en/stable/installation_and_usage.html +.. _here: https://black.readthedocs.io/en/stable/editor_integration.html +.. _pre-commit: https://pre-commit.com/ Profiling --------- diff --git a/docs/globals.rst b/docs/globals.rst index 54fd94df73..5d1469e437 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -96,6 +96,12 @@ Currently, the following resources and properties are being supported: # Properties of AWS::Serverless::HttpApi # Also works with Implicit APIs Auth: + CorsConfiguration: + AccessLogSettings: + Tags: + DefaultRouteSettings: + RouteSettings: + Domain: SimpleTable: # Properties of AWS::Serverless::SimpleTable diff --git a/examples/2016-10-31/custom_domains_with_route53/template.yaml b/examples/2016-10-31/custom_domains_with_route53/template.yaml index e4b560d703..b9b7dbb7d9 100644 --- a/examples/2016-10-31/custom_domains_with_route53/template.yaml +++ b/examples/2016-10-31/custom_domains_with_route53/template.yaml @@ -28,7 +28,7 @@ Resources: Path: /fetch MyApi: - Type: AWS::Serverless::Api + Type: AWS::Serverless::Api # Also works with HTTP API Properties: OpenApiVersion: 3.0.1 StageName: Prod diff --git a/examples/2016-10-31/http_api_cors/README.md b/examples/2016-10-31/http_api_cors/README.md new file mode 100644 index 0000000000..cb48bca40d --- /dev/null +++ b/examples/2016-10-31/http_api_cors/README.md @@ -0,0 +1,12 @@ +# HttpApi with CORS Example + +Example SAM template to configure CORS for HttpApi + +## Running the example + +```bash +$ sam deploy \ + --template-file template.yaml \ + --stack-name my-stack-name \ + --capabilities CAPABILITY_IAM +``` diff --git a/examples/2016-10-31/http_api_cors/template.yaml b/examples/2016-10-31/http_api_cors/template.yaml new file mode 100644 index 0000000000..9654dfc9d7 --- /dev/null +++ b/examples/2016-10-31/http_api_cors/template.yaml @@ -0,0 +1,48 @@ +AWSTemplateFormatVersion : '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: Http Api with Cors. + +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + console.log("Hello from MyAuthFunction") + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Handler: index.handler + Runtime: nodejs12.x + Events: + SimpleCase: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + FailOnWarnings: true + CorsConfiguration: + AllowHeaders: + - x-apigateway-header + AllowMethods: + - GET + AllowOrigins: + - https://foo.com + ExposeHeaders: + - x-amzn-header + DefinitionBody: + info: + version: '1.0' + title: + Ref: AWS::StackName + paths: + "$default": + x-amazon-apigateway-any-method: + isDefaultRoute: true + openapi: 3.0.1 \ No newline at end of file diff --git a/examples/2016-10-31/policy_templates/all_policy_templates.yaml b/examples/2016-10-31/policy_templates/all_policy_templates.yaml index ced335d421..27e05190cb 100644 --- a/examples/2016-10-31/policy_templates/all_policy_templates.yaml +++ b/examples/2016-10-31/policy_templates/all_policy_templates.yaml @@ -26,6 +26,9 @@ Resources: - DynamoDBReadPolicy: TableName: name + - DynamoDBWritePolicy: + TableName: name + - SESSendBouncePolicy: IdentityName: name @@ -38,6 +41,9 @@ Resources: - S3CrudPolicy: BucketName: name + - S3WritePolicy: + BucketName: name + - AMIDescribePolicy: {} - CloudFormationDescribeStacksPolicy: {} @@ -107,3 +113,6 @@ Resources: - TextractDetectAnalyzePolicy: {} - TextractGetResultPolicy: {} + + - EventBridgePutEventsPolicy: + EventBusName: name diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 6df86cae31..eb9596372c 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.21.0" +__version__ = "1.22.0" diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 204f180c7a..1dc2caa1c5 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -18,7 +18,7 @@ from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration from samtranslator.swagger.swagger import SwaggerEditor -from samtranslator.model.intrinsics import is_instrinsic, fnSub +from samtranslator.model.intrinsics import is_intrinsic, fnSub from samtranslator.model.lambda_ import LambdaPermission from samtranslator.translator import logical_id_generator from samtranslator.translator.arn_generator import ArnGenerator @@ -154,7 +154,7 @@ def _construct_rest_api(self): if self.definition_uri and self.definition_body: raise InvalidResourceException( - self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" + self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." ) if self.open_api_version: @@ -162,7 +162,7 @@ def _construct_rest_api(self): SwaggerEditor.get_openapi_versions_supported_regex(), self.open_api_version ): raise InvalidResourceException( - self.logical_id, 'The OpenApiVersion value must be of the format "3.0.0"' + self.logical_id, "The OpenApiVersion value must be of the format '3.0.0'." ) self._add_cors() @@ -193,7 +193,7 @@ def _construct_body_s3_dict(self): if not self.definition_uri.get("Bucket", None) or not self.definition_uri.get("Key", None): # DefinitionUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( - self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified" + self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified." ) s3_pointer = self.definition_uri @@ -205,7 +205,7 @@ def _construct_body_s3_dict(self): raise InvalidResourceException( self.logical_id, "'DefinitionUri' is not a valid S3 Uri of the form " - '"s3://bucket/key" with optional versionId query parameter.', + "'s3://bucket/key' with optional versionId query parameter.", ) body_s3 = {"Bucket": s3_pointer["Bucket"], "Key": s3_pointer["Key"]} @@ -276,7 +276,7 @@ def _construct_api_domain(self, rest_api): if self.domain.get("DomainName") is None or self.domain.get("CertificateArn") is None: raise InvalidResourceException( - self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided" + self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided." ) self.domain["ApiDomainName"] = "{}{}".format( @@ -293,7 +293,7 @@ def _construct_api_domain(self, rest_api): elif endpoint not in ["EDGE", "REGIONAL"]: raise InvalidResourceException( self.logical_id, - "EndpointConfiguration for Custom Domains must be" " one of {}".format(["EDGE", "REGIONAL"]), + "EndpointConfiguration for Custom Domains must be" " one of {}.".format(["EDGE", "REGIONAL"]), ) if endpoint == "REGIONAL": @@ -427,10 +427,10 @@ def _add_cors(self): if self.cors and not self.definition_body: raise InvalidResourceException( - self.logical_id, "Cors works only with inline Swagger specified in " "'DefinitionBody' property" + self.logical_id, "Cors works only with inline Swagger specified in 'DefinitionBody' property." ) - if isinstance(self.cors, string_types) or is_instrinsic(self.cors): + if isinstance(self.cors, string_types) or is_intrinsic(self.cors): # Just set Origin property. Others will be defaults properties = CorsProperties(AllowOrigin=self.cors) elif isinstance(self.cors, dict): @@ -447,7 +447,8 @@ def _add_cors(self): if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Cors configuration because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Cors configuration because " + "'DefinitionBody' does not contain a valid Swagger definition.", ) if properties.AllowCredentials is True and properties.AllowOrigin == _CORS_WILDCARD: @@ -500,7 +501,7 @@ def _add_auth(self): if self.auth and not self.definition_body: raise InvalidResourceException( - self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property" + self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property." ) # Make sure keys in the dict are recognized @@ -510,7 +511,8 @@ def _add_auth(self): if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Auth configuration because " + "'DefinitionBody' does not contain a valid Swagger definition.", ) swagger_editor = SwaggerEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) @@ -565,10 +567,10 @@ def _construct_usage_plan(self, rest_api_stage=None): usage_plan_key = None if create_usage_plan is None: - raise InvalidResourceException(self.logical_id, "'CreateUsagePlan' is a required field for UsagePlan") + raise InvalidResourceException(self.logical_id, "'CreateUsagePlan' is a required field for UsagePlan.") if create_usage_plan not in create_usage_plans_accepted_values: raise InvalidResourceException( - self.logical_id, "'CreateUsagePlan' accepts one of {}".format(create_usage_plans_accepted_values) + self.logical_id, "'CreateUsagePlan' accepts one of {}.".format(create_usage_plans_accepted_values) ) if create_usage_plan == "NONE": @@ -683,7 +685,7 @@ def _add_gateway_responses(self): if self.gateway_responses and not self.definition_body: raise InvalidResourceException( self.logical_id, - "GatewayResponses works only with inline Swagger specified in " "'DefinitionBody' property", + "GatewayResponses works only with inline Swagger specified in " "'DefinitionBody' property.", ) # Make sure keys in the dict are recognized @@ -692,13 +694,16 @@ def _add_gateway_responses(self): if response_key not in GatewayResponseProperties: raise InvalidResourceException( self.logical_id, - "Invalid property '{}' in 'GatewayResponses' property '{}'".format(response_key, responses_key), + "Invalid property '{}' in 'GatewayResponses' property '{}'.".format( + response_key, responses_key + ), ) if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Auth configuration because " + "'DefinitionBody' does not contain a valid Swagger definition.", ) swagger_editor = SwaggerEditor(self.definition_body) @@ -729,13 +734,14 @@ def _add_models(self): if self.models and not self.definition_body: raise InvalidResourceException( - self.logical_id, "Models works only with inline Swagger specified in " "'DefinitionBody' property" + self.logical_id, "Models works only with inline Swagger specified in " "'DefinitionBody' property." ) if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Models definitions because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Models definitions because " + "'DefinitionBody' does not contain a valid Swagger definition.", ) if not all(isinstance(model, dict) for model in self.models.values()): @@ -818,7 +824,7 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): return None if not isinstance(authorizers_config, dict): - raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary") + raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): @@ -895,7 +901,9 @@ def _set_default_authorizer( if not authorizers.get(default_authorizer) and default_authorizer != "AWS_IAM": raise InvalidResourceException( self.logical_id, - "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'", + "Unable to set DefaultAuthorizer because '" + + default_authorizer + + "' was not defined in 'Authorizers'.", ) for path in swagger_editor.iter_on_path(): diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index b3d465cfa4..afd9c40731 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -1,16 +1,32 @@ +import re from collections import namedtuple from six import string_types -from samtranslator.model.intrinsics import ref -from samtranslator.model.apigatewayv2 import ApiGatewayV2HttpApi, ApiGatewayV2Stage, ApiGatewayV2Authorizer +from samtranslator.model.intrinsics import ref, fnGetAtt +from samtranslator.model.apigatewayv2 import ( + ApiGatewayV2HttpApi, + ApiGatewayV2Stage, + ApiGatewayV2Authorizer, + ApiGatewayV2DomainName, + ApiGatewayV2ApiMapping, +) from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.open_api.open_api import OpenApiEditor from samtranslator.translator import logical_id_generator from samtranslator.model.tags.resource_tagging import get_tag_list +from samtranslator.model.intrinsics import is_intrinsic +from samtranslator.model.route53 import Route53RecordSetGroup + +_CORS_WILDCARD = "*" +CorsProperties = namedtuple( + "_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigins", "MaxAge", "ExposeHeaders", "AllowCredentials"] +) +CorsProperties.__new__.__defaults__ = (None, None, None, None, None, False) AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"]) AuthProperties.__new__.__defaults__ = (None, None) DefaultStageName = "$default" +HttpApiTagName = "httpapi:createdBy" class HttpApiGenerator(object): @@ -24,9 +40,14 @@ def __init__( stage_name, tags=None, auth=None, + cors_configuration=None, access_log_settings=None, + route_settings=None, + default_route_settings=None, resource_attributes=None, passthrough_resource_attributes=None, + domain=None, + fail_on_warnings=False, ): """Constructs an API Generator class that generates API Gateway resources @@ -51,10 +72,15 @@ def __init__( if not self.stage_name: self.stage_name = DefaultStageName self.auth = auth + self.cors_configuration = cors_configuration self.tags = tags self.access_log_settings = access_log_settings + self.route_settings = route_settings + self.default_route_settings = default_route_settings self.resource_attributes = resource_attributes self.passthrough_resource_attributes = passthrough_resource_attributes + self.domain = domain + self.fail_on_warnings = fail_on_warnings def _construct_http_api(self): """Constructs and returns the ApiGatewayV2 HttpApi. @@ -66,10 +92,17 @@ def _construct_http_api(self): if self.definition_uri and self.definition_body: raise InvalidResourceException( - self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" + self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." ) + if self.cors_configuration: + # call this method to add cors in open api + self._add_cors() self._add_auth() + self._add_tags() + + if self.fail_on_warnings: + http_api.FailOnWarnings = self.fail_on_warnings if self.definition_uri: http_api.BodyS3Location = self._construct_body_s3_dict() @@ -80,14 +113,214 @@ def _construct_http_api(self): self.logical_id, "'DefinitionUri' or 'DefinitionBody' are required properties of an " "'AWS::Serverless::HttpApi'. Add a value for one of these properties or " - "add a 'HttpApi' event to an 'AWS::Serverless::Function'", + "add a 'HttpApi' event to an 'AWS::Serverless::Function'.", ) - if self.tags is not None: - http_api.Tags = get_tag_list(self.tags) - return http_api + def _add_cors(self): + """ + Add CORS configuration if CORSConfiguration property is set in SAM. + Adds CORS configuration only if DefinitionBody is present and + APIGW extension for CORS is not present in the DefinitionBody + """ + + if self.cors_configuration and not self.definition_body: + raise InvalidResourceException( + self.logical_id, "Cors works only with inline OpenApi specified in 'DefinitionBody' property." + ) + + # If cors configuration is set to true add * to the allow origins. + # This also support referencing the value as a parameter + if isinstance(self.cors_configuration, bool): + # if cors config is true add Origins as "'*'" + properties = CorsProperties(AllowOrigins=[_CORS_WILDCARD]) + + elif is_intrinsic(self.cors_configuration): + # Just set Origin property. Intrinsics will be handledOthers will be defaults + properties = CorsProperties(AllowOrigins=self.cors_configuration) + + elif isinstance(self.cors_configuration, dict): + # Make sure keys in the dict are recognized + if not all(key in CorsProperties._fields for key in self.cors_configuration.keys()): + raise InvalidResourceException(self.logical_id, "Invalid value for 'Cors' property.") + + properties = CorsProperties(**self.cors_configuration) + + else: + raise InvalidResourceException(self.logical_id, "Invalid value for 'Cors' property.") + + if not OpenApiEditor.is_valid(self.definition_body): + raise InvalidResourceException( + self.logical_id, + "Unable to add Cors configuration because " + "'DefinitionBody' does not contain a valid " + "OpenApi definition.", + ) + + if properties.AllowCredentials is True and properties.AllowOrigins == [_CORS_WILDCARD]: + raise InvalidResourceException( + self.logical_id, + "Unable to add Cors configuration because " + "'AllowCredentials' can not be true when " + "'AllowOrigin' is \"'*'\" or not set.", + ) + + editor = OpenApiEditor(self.definition_body) + # if CORS is set in both definition_body and as a CorsConfiguration property, + # SAM merges and overrides the cors headers in definition_body with headers of CorsConfiguration + editor.add_cors( + properties.AllowOrigins, + properties.AllowHeaders, + properties.AllowMethods, + properties.ExposeHeaders, + properties.MaxAge, + properties.AllowCredentials, + ) + + # Assign the OpenApi back to template + self.definition_body = editor.openapi + + def _construct_api_domain(self, http_api): + """ + Constructs and returns the ApiGateway Domain and BasepathMapping + """ + if self.domain is None: + return None, None, None + + if self.domain.get("DomainName") is None or self.domain.get("CertificateArn") is None: + raise InvalidResourceException( + self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided." + ) + + self.domain["ApiDomainName"] = "{}{}".format( + "ApiGatewayDomainNameV2", logical_id_generator.LogicalIdGenerator("", self.domain.get("DomainName")).gen() + ) + + domain = ApiGatewayV2DomainName( + self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes + ) + domain_config = dict() + domain.DomainName = self.domain.get("DomainName") + domain.Tags = self.tags + endpoint = self.domain.get("EndpointConfiguration") + + if endpoint is None: + endpoint = "REGIONAL" + # to make sure that default is always REGIONAL + self.domain["EndpointConfiguration"] = "REGIONAL" + elif endpoint not in ["REGIONAL"]: + raise InvalidResourceException( + self.logical_id, "EndpointConfiguration for Custom Domains must be one of {}.".format(["REGIONAL"]), + ) + domain_config["EndpointType"] = endpoint + domain_config["CertificateArn"] = self.domain.get("CertificateArn") + + domain.DomainNameConfigurations = [domain_config] + + # Create BasepathMappings + if self.domain.get("BasePath") and isinstance(self.domain.get("BasePath"), string_types): + basepaths = [self.domain.get("BasePath")] + elif self.domain.get("BasePath") and isinstance(self.domain.get("BasePath"), list): + basepaths = self.domain.get("BasePath") + else: + basepaths = None + basepath_resource_list = self._construct_basepath_mappings(basepaths, http_api) + + # Create the Route53 RecordSetGroup resource + record_set_group = self._construct_route53_recordsetgroup() + + return domain, basepath_resource_list, record_set_group + + def _construct_route53_recordsetgroup(self): + record_set_group = None + if self.domain.get("Route53") is not None: + route53 = self.domain.get("Route53") + if route53.get("HostedZoneId") is None and route53.get("HostedZoneName") is None: + raise InvalidResourceException( + self.logical_id, + "HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.", + ) + 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 + ) + if "HostedZoneId" in route53: + record_set_group.HostedZoneId = route53.get("HostedZoneId") + elif "HostedZoneName" in route53: + record_set_group.HostedZoneName = route53.get("HostedZoneName") + record_set_group.RecordSets = self._construct_record_sets_for_domain(self.domain) + + return record_set_group + + def _construct_basepath_mappings(self, basepaths, http_api): + basepath_resource_list = [] + + if basepaths is None: + basepath_mapping = ApiGatewayV2ApiMapping( + self.logical_id + "ApiMapping", attributes=self.passthrough_resource_attributes + ) + basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) + basepath_mapping.ApiId = ref(http_api.logical_id) + basepath_mapping.Stage = ref(http_api.logical_id + ".Stage") + basepath_resource_list.extend([basepath_mapping]) + else: + for path in basepaths: + # search for invalid characters in the path and raise error if there are + invalid_regex = r"[^0-9a-zA-Z\/\-\_]+" + if re.search(invalid_regex, path) is not None: + raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.") + # ignore leading and trailing `/` in the path name + m = re.search(r"[a-zA-Z0-9]+[\-\_]?[a-zA-Z0-9]+", path) + path = m.string[m.start(0) : m.end(0)] + if path is None: + raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.") + logical_id = "{}{}{}".format(self.logical_id, re.sub(r"[\-\_]+", "", path), "ApiMapping") + basepath_mapping = ApiGatewayV2ApiMapping(logical_id, attributes=self.passthrough_resource_attributes) + basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) + basepath_mapping.ApiId = ref(http_api.logical_id) + basepath_mapping.Stage = ref(http_api.logical_id + ".Stage") + basepath_mapping.ApiMappingKey = path + basepath_resource_list.extend([basepath_mapping]) + return basepath_resource_list + + 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"): + 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: + raise InvalidResourceException( + self.logical_id, "Only REGIONAL endpoint is supported on HTTP APIs.", + ) + return alias_target + def _add_auth(self): """ Add Auth configuration to the OAS file, if necessary @@ -97,7 +330,7 @@ def _add_auth(self): if self.auth and not self.definition_body: raise InvalidResourceException( - self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property" + self.logical_id, "Auth works only with inline OpenApi specified in the 'DefinitionBody' property." ) # Make sure keys in the dict are recognized @@ -107,7 +340,7 @@ def _add_auth(self): if not OpenApiEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, - "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger", + "Unable to add Auth configuration because 'DefinitionBody' does not contain a valid OpenApi definition.", ) open_api_editor = OpenApiEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) @@ -120,6 +353,36 @@ def _add_auth(self): ) self.definition_body = open_api_editor.openapi + def _add_tags(self): + """ + Adds tags to the Http Api, including a default SAM tag. + """ + if self.tags and not self.definition_body: + raise InvalidResourceException( + self.logical_id, "Tags works only with inline OpenApi specified in the 'DefinitionBody' property." + ) + + if not self.definition_body: + return + + if self.tags and not OpenApiEditor.is_valid(self.definition_body): + raise InvalidResourceException( + self.logical_id, + "Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition.", + ) + elif not OpenApiEditor.is_valid(self.definition_body): + return + + if not self.tags: + self.tags = {} + self.tags[HttpApiTagName] = "SAM" + + open_api_editor = OpenApiEditor(self.definition_body) + + # authorizers is guaranteed to return a value or raise an exception + open_api_editor.add_tags(self.tags) + self.definition_body = open_api_editor.openapi + def _set_default_authorizer(self, open_api_editor, authorizers, default_authorizer, api_authorizers): """ Sets the default authorizer if one is given in the template @@ -134,7 +397,9 @@ def _set_default_authorizer(self, open_api_editor, authorizers, default_authoriz if not authorizers.get(default_authorizer): raise InvalidResourceException( self.logical_id, - "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'", + "Unable to set DefaultAuthorizer because '" + + default_authorizer + + "' was not defined in 'Authorizers'.", ) for path in open_api_editor.iter_on_path(): @@ -151,7 +416,7 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): authorizers = {} if not isinstance(authorizers_config, dict): - raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary") + raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): @@ -159,10 +424,15 @@ def _get_authorizers(self, authorizers_config, default_authorizer=None): self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name) ) + if "OpenIdConnectUrl" in authorizer: + raise InvalidResourceException( + self.logical_id, + "'OpenIdConnectUrl' is no longer a supported property for authorizer '%s'. Please refer to the AWS SAM documentation." + % (authorizer_name), + ) authorizers[authorizer_name] = ApiGatewayV2Authorizer( api_logical_id=self.logical_id, name=authorizer_name, - open_id_connect_url=authorizer.get("OpenIdConnectUrl"), authorization_scopes=authorizer.get("AuthorizationScopes"), jwt_configuration=authorizer.get("JwtConfiguration"), id_source=authorizer.get("IdentitySource"), @@ -179,7 +449,7 @@ def _construct_body_s3_dict(self): if not self.definition_uri.get("Bucket", None) or not self.definition_uri.get("Key", None): # DefinitionUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( - self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified" + self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified." ) s3_pointer = self.definition_uri @@ -190,7 +460,7 @@ def _construct_body_s3_dict(self): raise InvalidResourceException( self.logical_id, "'DefinitionUri' is not a valid S3 Uri of the form " - '"s3://bucket/key" with optional versionId query parameter.', + "'s3://bucket/key' with optional versionId query parameter.", ) body_s3 = {"Bucket": s3_pointer["Bucket"], "Key": s3_pointer["Key"]} @@ -206,7 +476,13 @@ def _construct_stage(self): """ # If there are no special configurations, don't create a stage and use the default - if not self.stage_name and not self.stage_variables and not self.access_log_settings: + if ( + not self.stage_name + and not self.stage_variables + and not self.access_log_settings + and not self.default_route_settings + and not self.route_settings + ): return # If StageName is some intrinsic function, then don't prefix the Stage's logical ID @@ -224,21 +500,21 @@ def _construct_stage(self): stage.StageName = self.stage_name stage.StageVariables = self.stage_variables stage.AccessLogSettings = self.access_log_settings + stage.DefaultRouteSettings = self.default_route_settings + stage.Tags = self.tags stage.AutoDeploy = True - - if self.tags is not None: - stage.Tags = get_tag_list(self.tags) + stage.RouteSettings = self.route_settings return stage def to_cloudformation(self): - """Generates CloudFormation resources from a SAM API resource + """Generates CloudFormation resources from a SAM HTTP API resource :returns: a tuple containing the HttpApi and Stage for an empty Api. :rtype: tuple """ http_api = self._construct_http_api() - + domain, basepath_mapping, route53 = self._construct_api_domain(http_api) stage = self._construct_stage() - return http_api, stage + return http_api, stage, domain, basepath_mapping, route53 diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index e1bfaa2820..7494f6729b 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -233,7 +233,8 @@ def __init__( ): if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: raise InvalidResourceException( - api_logical_id, name + " Authorizer has invalid " "'FunctionPayloadType': " + function_payload_type + api_logical_id, + name + " Authorizer has invalid " "'FunctionPayloadType': " + function_payload_type + ".", ) if function_payload_type == "REQUEST" and self._is_missing_identity_source(identity): diff --git a/samtranslator/model/apigatewayv2.py b/samtranslator/model/apigatewayv2.py index 93f0fdec6d..1345cd79a9 100644 --- a/samtranslator/model/apigatewayv2.py +++ b/samtranslator/model/apigatewayv2.py @@ -12,7 +12,6 @@ class ApiGatewayV2HttpApi(Resource): "Description": PropertyType(False, is_str()), "FailOnWarnings": PropertyType(False, is_type(bool)), "BasePath": PropertyType(False, is_str()), - "Tags": PropertyType(False, list_of(is_type(dict))), "CorsConfiguration": PropertyType(False, is_type(dict)), } @@ -24,11 +23,12 @@ class ApiGatewayV2Stage(Resource): property_types = { "AccessLogSettings": PropertyType(False, is_type(dict)), "DefaultRouteSettings": PropertyType(False, is_type(dict)), + "RouteSettings": PropertyType(False, is_type(dict)), "ClientCertificateId": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), "ApiId": PropertyType(True, is_str()), "StageName": PropertyType(False, one_of(is_str(), is_type(dict))), - "Tags": PropertyType(False, list_of(is_type(dict))), + "Tags": PropertyType(False, is_type(dict)), "StageVariables": PropertyType(False, is_type(dict)), "AutoDeploy": PropertyType(False, is_type(bool)), } @@ -36,35 +36,45 @@ class ApiGatewayV2Stage(Resource): runtime_attrs = {"stage_name": lambda self: ref(self.logical_id)} +class ApiGatewayV2DomainName(Resource): + resource_type = "AWS::ApiGatewayV2::DomainName" + property_types = { + "DomainName": PropertyType(True, is_str()), + "DomainNameConfigurations": PropertyType(False, list_of(is_type(dict))), + "Tags": PropertyType(False, is_type(dict)), + } + + +class ApiGatewayV2ApiMapping(Resource): + resource_type = "AWS::ApiGatewayV2::ApiMapping" + property_types = { + "ApiId": PropertyType(True, is_str()), + "ApiMappingKey": PropertyType(False, is_str()), + "DomainName": PropertyType(True, is_str()), + "Stage": PropertyType(True, is_str()), + } + + class ApiGatewayV2Authorizer(object): def __init__( - self, - api_logical_id=None, - name=None, - open_id_connect_url=None, - authorization_scopes=[], - jwt_configuration={}, - id_source=None, + self, api_logical_id=None, name=None, authorization_scopes=[], jwt_configuration={}, id_source=None, ): """ Creates an authorizer for use in V2 Http Apis """ - # OIDC uses a connect url, oauth2 doesn't - self.auth_type = "openIdConnect" - if open_id_connect_url is None: - self.auth_type = "oauth2" + # Currently only one type of auth + self.auth_type = "oauth2" self.api_logical_id = api_logical_id self.name = name - self.open_id_connect_url = open_id_connect_url self.authorization_scopes = authorization_scopes # Validate necessary parameters exist if not jwt_configuration: - raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'JwtConfiguration'") + raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'JwtConfiguration'.") self.jwt_configuration = jwt_configuration if not id_source: - raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'IdentitySource'") + raise InvalidResourceException(api_logical_id, name + " Authorizer must define 'IdentitySource'.") self.id_source = id_source def generate_openapi(self): @@ -79,6 +89,4 @@ def generate_openapi(self): "type": "jwt", }, } - if self.open_id_connect_url: - openapi["x-amazon-apigateway-authorizer"]["openIdConnectUrl"] = self.open_id_connect_url return openapi diff --git a/samtranslator/model/cognito.py b/samtranslator/model/cognito.py index a78cfae1bd..bd9a830319 100644 --- a/samtranslator/model/cognito.py +++ b/samtranslator/model/cognito.py @@ -23,7 +23,7 @@ class CognitoUserPool(Resource): "UsernameAttributes": PropertyType(False, list_of(is_str())), "UserPoolAddOns": PropertyType(False, list_of(dict)), "UserPoolName": PropertyType(False, is_str()), - "UserPoolTags": PropertyType(False, is_str()), + "UserPoolTags": PropertyType(False, is_type(dict)), "VerificationMessageTemplate": PropertyType(False, is_type(dict)), } diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 2ccb40d32e..66aea268e6 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -85,6 +85,8 @@ def to_cloudformation(self, **kwargs): # SAM attaches the policies for SQS or SNS only if 'Type' is given if destination_type: + # delete this field as its used internally for SAM to determine the policy + del self.DestinationConfig["OnFailure"]["Type"] # the values 'SQS' and 'SNS' are allowed. No intrinsics are allowed if destination_type not in ["SQS", "SNS"]: raise InvalidEventException(self.logical_id, "The only valid values for 'Type' are 'SQS' and 'SNS'") @@ -102,7 +104,6 @@ def to_cloudformation(self, **kwargs): destination_config_policy = IAMRolePolicies().sns_publish_role_policy( sns_topic_arn, self.logical_id ) - lambda_eventsourcemapping.DestinationConfig = self.DestinationConfig if "Condition" in function.resource_attributes: diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 378241a38d..03ec6a8fb6 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -937,6 +937,9 @@ class HttpApi(PushEventSource): "ApiId": PropertyType(False, is_str()), "Stage": PropertyType(False, is_str()), "Auth": PropertyType(False, is_type(dict)), + "TimeoutInMillis": PropertyType(False, is_type(int)), + "RouteSettings": PropertyType(False, is_type(dict)), + "PayloadFormatVersion": PropertyType(False, is_str()), } def resources_to_link(self, resources): @@ -1066,6 +1069,18 @@ def _add_openapi_integration(self, api, function, manage_swagger=False): editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get("Auth"), condition=condition) if self.Auth: self._add_auth_to_openapi_integration(api, editor) + if self.TimeoutInMillis: + editor.add_timeout_to_method(api=api, path=self.Path, method_name=self.Method, timeout=self.TimeoutInMillis) + path_parameters = re.findall("{(.*?)}", self.Path) + if path_parameters: + editor.add_path_parameters_to_method( + api=api, path=self.Path, method_name=self.Method, path_parameters=path_parameters + ) + + if self.PayloadFormatVersion: + editor.add_payload_format_version_to_method( + api=api, path=self.Path, method_name=self.Method, payload_format_version=self.PayloadFormatVersion + ) api["DefinitionBody"] = editor.openapi def _add_auth_to_openapi_integration(self, api, editor): diff --git a/samtranslator/model/function_policies.py b/samtranslator/model/function_policies.py index 929d498ee7..91a5be28a1 100644 --- a/samtranslator/model/function_policies.py +++ b/samtranslator/model/function_policies.py @@ -3,7 +3,7 @@ from six import string_types -from samtranslator.model.intrinsics import is_instrinsic, is_intrinsic_if, is_intrinsic_no_value +from samtranslator.model.intrinsics import is_intrinsic, is_intrinsic_if, is_intrinsic_no_value from samtranslator.model.exceptions import InvalidTemplateException PolicyEntry = namedtuple("PolicyEntry", "data type") @@ -126,7 +126,7 @@ def _get_type(self, policy): return self._get_type_from_intrinsic_if(policy) # Intrinsic functions are treated as managed policies by default - if is_instrinsic(policy): + if is_intrinsic(policy): return PolicyTypes.MANAGED_POLICY # Policy statement is a dictionary with the key "Statement" in it diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index 18dccc8ee0..57982f5148 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -129,7 +129,7 @@ def make_shorthand(intrinsic_dict): raise NotImplementedError("Shorthanding is only supported for Ref and Fn::GetAtt") -def is_instrinsic(input): +def is_intrinsic(input): """ Checks if the given input is an intrinsic function dictionary. Intrinsic function is a dictionary with single key that is the name of the intrinsics. @@ -155,7 +155,7 @@ def is_intrinsic_if(input): :return: True, if yes """ - if not is_instrinsic(input): + if not is_intrinsic(input): return False key = list(input.keys())[0] @@ -171,7 +171,7 @@ def is_intrinsic_no_value(input): :return: True, if yes """ - if not is_instrinsic(input): + if not is_intrinsic(input): return False key = list(input.keys())[0] diff --git a/samtranslator/model/preferences/deployment_preference_collection.py b/samtranslator/model/preferences/deployment_preference_collection.py index 2f0bc35ac1..d259658bba 100644 --- a/samtranslator/model/preferences/deployment_preference_collection.py +++ b/samtranslator/model/preferences/deployment_preference_collection.py @@ -2,7 +2,7 @@ from samtranslator.model.codedeploy import CodeDeployApplication from samtranslator.model.codedeploy import CodeDeployDeploymentGroup from samtranslator.model.iam import IAMRole -from samtranslator.model.intrinsics import fnSub, is_instrinsic +from samtranslator.model.intrinsics import fnSub, is_intrinsic from samtranslator.model.update_policy import UpdatePolicy from samtranslator.translator.arn_generator import ArnGenerator import copy @@ -149,7 +149,7 @@ def _replace_deployment_types(self, value, key=None): for i in range(len(value)): value[i] = self._replace_deployment_types(value[i]) return value - elif is_instrinsic(value): + elif is_intrinsic(value): for (k, v) in value.items(): value[k] = self._replace_deployment_types(v, k) return value diff --git a/samtranslator/model/s3_utils/uri_parser.py b/samtranslator/model/s3_utils/uri_parser.py index e55a6a6820..a5188e21c0 100644 --- a/samtranslator/model/s3_utils/uri_parser.py +++ b/samtranslator/model/s3_utils/uri_parser.py @@ -59,7 +59,7 @@ def construct_s3_location_object(location_uri, logical_id, property_name): if not location_uri.get("Bucket") or not location_uri.get("Key"): # location_uri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( - logical_id, "'{}' requires Bucket and Key properties to be " "specified".format(property_name) + logical_id, "'{}' requires Bucket and Key properties to be specified.".format(property_name) ) s3_pointer = location_uri @@ -72,7 +72,7 @@ def construct_s3_location_object(location_uri, logical_id, property_name): raise InvalidResourceException( logical_id, "'{}' is not a valid S3 Uri of the form " - '"s3://bucket/key" with optional versionId query ' + "'s3://bucket/key' with optional versionId query " "parameter.".format(property_name), ) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index dd6cc10b80..37c5ff6640 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -10,8 +10,15 @@ from .s3_utils.uri_parser import construct_s3_location_object from .tags.resource_tagging import get_tag_list from samtranslator.model import PropertyType, SamResourceMacro, ResourceTypeResolver -from samtranslator.model.apigateway import ApiGatewayDeployment, ApiGatewayStage, ApiGatewayDomainName -from samtranslator.model.apigatewayv2 import ApiGatewayV2Stage +from samtranslator.model.apigateway import ( + ApiGatewayDeployment, + ApiGatewayStage, + ApiGatewayDomainName, + ApiGatewayUsagePlan, + ApiGatewayUsagePlanKey, + ApiGatewayApiKey, +) +from samtranslator.model.apigatewayv2 import ApiGatewayV2Stage, ApiGatewayV2DomainName from samtranslator.model.cloudformation import NestedStack from samtranslator.model.dynamodb import DynamoDBTable from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException @@ -530,7 +537,7 @@ def _validate_dlq(self): if not self.DeadLetterQueue.get("Type") or not self.DeadLetterQueue.get("TargetArn"): raise InvalidResourceException( self.logical_id, - "'DeadLetterQueue' requires Type and TargetArn properties to be specified".format(valid_dlq_types), + "'DeadLetterQueue' requires Type and TargetArn properties to be specified.".format(valid_dlq_types), ) # Validate required Types @@ -654,7 +661,17 @@ def _construct_version(self, function, intrinsics_resolver, code_sha256=None): # SHA Collisions: For purposes of triggering a new update, we are concerned about just the difference previous # and next hashes. The chances that two subsequent hashes collide is fairly low. prefix = "{id}Version".format(id=self.logical_id) - logical_id = logical_id_generator.LogicalIdGenerator(prefix, code_dict, code_sha256).gen() + logical_dict = {} + try: + logical_dict = code_dict.copy() + except (AttributeError, UnboundLocalError): + pass + else: + if function.Environment: + logical_dict.update(function.Environment) + if function.MemorySize: + logical_dict.update({"MemorySize": function.MemorySize}) + logical_id = logical_id_generator.LogicalIdGenerator(prefix, logical_dict, code_sha256).gen() attributes = self.get_passthrough_resource_attributes() if attributes is None: @@ -715,7 +732,7 @@ def _validate_deployment_preference_and_add_update_policy( if deployment_preference_collection.get(self.logical_id).enabled: if self.AutoPublishAlias is None: raise InvalidResourceException( - self.logical_id, "'DeploymentPreference' requires AutoPublishAlias property to be specified" + self.logical_id, "'DeploymentPreference' requires AutoPublishAlias property to be specified." ) if lambda_alias is None: raise ValueError("lambda_alias expected for updating it with the appropriate update policy") @@ -764,6 +781,9 @@ class SamApi(SamResourceMacro): "Stage": ApiGatewayStage.resource_type, "Deployment": ApiGatewayDeployment.resource_type, "DomainName": ApiGatewayDomainName.resource_type, + "UsagePlan": ApiGatewayUsagePlan.resource_type, + "UsagePlanKey": ApiGatewayUsagePlanKey.resource_type, + "ApiKey": ApiGatewayApiKey.resource_type, } def to_cloudformation(self, **kwargs): @@ -852,15 +872,22 @@ class SamHttpApi(SamResourceMacro): "DefinitionBody": PropertyType(False, is_type(dict)), "DefinitionUri": PropertyType(False, one_of(is_str(), is_type(dict))), "StageVariables": PropertyType(False, is_type(dict)), - "Cors": PropertyType(False, one_of(is_str(), is_type(dict))), + "CorsConfiguration": PropertyType(False, one_of(is_type(bool), is_type(dict))), "AccessLogSettings": PropertyType(False, is_type(dict)), + "DefaultRouteSettings": PropertyType(False, is_type(dict)), "Auth": PropertyType(False, is_type(dict)), + "RouteSettings": PropertyType(False, is_type(dict)), + "Domain": PropertyType(False, is_type(dict)), + "FailOnWarnings": PropertyType(False, is_type(bool)), } - referable_properties = {"Stage": ApiGatewayV2Stage.resource_type} + referable_properties = { + "Stage": ApiGatewayV2Stage.resource_type, + "DomainName": ApiGatewayV2DomainName.resource_type, + } def to_cloudformation(self, **kwargs): - """Returns the API Gateway RestApi, Deployment, and Stage to which this SAM Api corresponds. + """Returns the API GatewayV2 Api, Deployment, and Stage to which this SAM Api corresponds. :param dict kwargs: already-converted resources that may need to be modified when converting this \ macro to pure CloudFormation @@ -868,6 +895,11 @@ def to_cloudformation(self, **kwargs): :rtype: list """ resources = [] + intrinsics_resolver = kwargs["intrinsics_resolver"] + self.CorsConfiguration = intrinsics_resolver.resolve_parameter_refs(self.CorsConfiguration) + + intrinsics_resolver = kwargs["intrinsics_resolver"] + self.Domain = intrinsics_resolver.resolve_parameter_refs(self.Domain) api_generator = HttpApiGenerator( self.logical_id, @@ -878,14 +910,25 @@ def to_cloudformation(self, **kwargs): self.StageName, tags=self.Tags, auth=self.Auth, + cors_configuration=self.CorsConfiguration, access_log_settings=self.AccessLogSettings, + route_settings=self.RouteSettings, + default_route_settings=self.DefaultRouteSettings, resource_attributes=self.resource_attributes, passthrough_resource_attributes=self.get_passthrough_resource_attributes(), + domain=self.Domain, + fail_on_warnings=self.FailOnWarnings, ) - http_api, stage = api_generator.to_cloudformation() + (http_api, stage, domain, basepath_mapping, route53,) = api_generator.to_cloudformation() resources.append(http_api) + if domain: + resources.append(domain) + if basepath_mapping: + resources.extend(basepath_mapping) + if route53: + resources.append(route53) # Stage is now optional. Only add it if one is created. if stage: diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 2d8ff46b4e..68b652cfb7 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -4,7 +4,9 @@ from samtranslator.model.intrinsics import ref from samtranslator.model.intrinsics import make_conditional +from samtranslator.model.intrinsics import is_intrinsic from samtranslator.model.exceptions import InvalidDocumentException, InvalidTemplateException +import json class OpenApiEditor(object): @@ -16,6 +18,8 @@ class OpenApiEditor(object): """ _X_APIGW_INTEGRATION = "x-amazon-apigateway-integration" + _X_APIGW_TAG_VALUE = "x-amazon-apigateway-tag-value" + _X_APIGW_CORS = "x-amazon-apigateway-cors" _CONDITIONAL_IF = "Fn::If" _X_ANY_METHOD = "x-amazon-apigateway-any-method" _ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"] @@ -26,8 +30,8 @@ def __init__(self, doc): Initialize the class with a swagger dictionary. This class creates a copy of the Swagger and performs all modifications on this copy. - :param dict doc: Swagger document as a dictionary - :raises ValueError: If the input Swagger document does not meet the basic Swagger requirements. + :param dict doc: OpenApi document as a dictionary + :raises ValueError: If the input OpenApi document does not meet the basic OpenApi requirements. """ if not OpenApiEditor.is_valid(doc): raise ValueError( @@ -39,6 +43,7 @@ def __init__(self, doc): self.paths = self._doc["paths"] self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {}) self.definitions = self._doc.get("definitions", {}) + self.tags = self._doc.get("tags", []) def get_path(self, path): """ @@ -85,10 +90,12 @@ def get_integration_function_logical_id(self, path_name, method_name): # Extract the integration uri out of a conditional if necessary uri = integration.get("uri") + if not isinstance(uri, dict): + return "" if self._CONDITIONAL_IF in uri: arn = uri[self._CONDITIONAL_IF][1].get("Fn::Sub") else: - arn = uri.get("Fn::Sub") + arn = uri.get("Fn::Sub", "") # Extract lambda integration (${LambdaName.Arn}) and split ".Arn" off from it regex = "([A-Za-z0-9]+\.Arn)" @@ -203,7 +210,7 @@ def add_lambda_integration( path_dict[method][self._X_APIGW_INTEGRATION] = { "type": "aws_proxy", "httpMethod": "POST", - "payloadFormatVersion": "1.0", + "payloadFormatVersion": "2.0", "uri": integration_uri, } @@ -236,6 +243,66 @@ def iter_on_path(self): for path, value in self.paths.items(): yield path + def add_timeout_to_method(self, api, path, method_name, timeout): + """ + Adds a timeout to this path/method. + + :param dict api: Reference to the related Api's properties as defined in the template. + :param string path: Path name + :param string method_name: Method name + :param int timeout: Timeout amount, in milliseconds + """ + normalized_method_name = self._normalize_method_name(method_name) + for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + if self.method_definition_has_integration(method_definition): + method_definition[self._X_APIGW_INTEGRATION]["timeoutInMillis"] = timeout + + def add_path_parameters_to_method(self, api, path, method_name, path_parameters): + """ + Adds path parameters to this path + method + + :param dict api: Reference to the related Api's properties as defined in the template. + :param string path: Path name + :param string method_name: Method name + :param list path_parameters: list of strings of path parameters + """ + normalized_method_name = self._normalize_method_name(method_name) + for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + # create path parameter list + # add it here if it doesn't exist, merge with existing otherwise. + method_definition.setdefault("parameters", []) + for param in path_parameters: + # find an existing parameter with this name if it exists + existing_parameter = next( + ( + existing_parameter + for existing_parameter in method_definition.get("parameters", []) + if existing_parameter.get("name") == param + ), + None, + ) + if existing_parameter: + # overwrite parameter values for existing path parameter + existing_parameter["in"] = "path" + existing_parameter["required"] = True + else: + parameter = {"name": param, "in": "path", "required": True} + method_definition.get("parameters").append(parameter) + + def add_payload_format_version_to_method(self, api, path, method_name, payload_format_version="2.0"): + """ + Adds a payload format version to this path/method. + + :param dict api: Reference to the related Api's properties as defined in the template. + :param string path: Path name + :param string method_name: Method name + :param string payload_format_version: payload format version sent to the integration + """ + normalized_method_name = self._normalize_method_name(method_name) + for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + if self.method_definition_has_integration(method_definition): + method_definition[self._X_APIGW_INTEGRATION]["payloadFormatVersion"] = payload_format_version + def add_authorizers_security_definitions(self, authorizers): """ Add Authorizer definitions to the securityDefinitions part of Swagger. @@ -344,6 +411,90 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers if security: method_definition["security"] = security + def add_tags(self, tags): + """ + Adds tags to the OpenApi definition using an ApiGateway extension for tag values. + + :param dict tags: dictionary of tagName:tagValue pairs. + """ + for name, value in tags.items(): + # find an existing tag with this name if it exists + existing_tag = next((existing_tag for existing_tag in self.tags if existing_tag.get("name") == name), None) + if existing_tag: + # overwrite tag value for an existing tag + existing_tag[self._X_APIGW_TAG_VALUE] = value + else: + tag = {"name": name, self._X_APIGW_TAG_VALUE: value} + self.tags.append(tag) + + def add_cors( + self, + allow_origins, + allow_headers=None, + allow_methods=None, + expose_headers=None, + max_age=None, + allow_credentials=None, + ): + """ + Add CORS configuration to this Api to _X_APIGW_CORS header in open api definition + + Following this guide: + https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html + https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-api-cors.html + + :param list/dict allowed_origins: Comma separate list of allowed origins. + Value can also be an intrinsic function dict. + :param list/dict allowed_headers: Comma separated list of allowed headers. + Value can also be an intrinsic function dict. + :param list/dict allowed_methods: Comma separated list of allowed methods. + Value can also be an intrinsic function dict. + :param list/dict expose_headers: Comma separated list of allowed methods. + Value can also be an intrinsic function dict. + :param integer/dict max_age: Maximum duration to cache the CORS Preflight request. Value is set on + Access-Control-Max-Age header. Value can also be an intrinsic function dict. + :param bool/None allowed_credentials: Flags whether request is allowed to contain credentials. + """ + ALLOW_ORIGINS = "allowOrigins" + ALLOW_HEADERS = "allowHeaders" + ALLOW_METHODS = "allowMethods" + EXPOSE_HEADERS = "exposeHeaders" + MAX_AGE = "maxAge" + ALLOW_CREDENTIALS = "allowCredentials" + cors_headers = [ALLOW_ORIGINS, ALLOW_HEADERS, ALLOW_METHODS, EXPOSE_HEADERS, MAX_AGE, ALLOW_CREDENTIALS] + cors_configuration = self._doc.get(self._X_APIGW_CORS, dict()) + + # intrinsics will not work if cors configuration is defined in open api and as a property to the HttpApi + if allow_origins and is_intrinsic(allow_origins): + cors_configuration_string = json.dumps(allow_origins) + for header in cors_headers: + # example: allowOrigins to AllowOrigins + keyword = header[0].upper() + header[1:] + cors_configuration_string = cors_configuration_string.replace(keyword, header) + cors_configuration_dict = json.loads(cors_configuration_string) + cors_configuration.update(cors_configuration_dict) + + else: + if allow_origins: + cors_configuration[ALLOW_ORIGINS] = allow_origins + if allow_headers: + cors_configuration[ALLOW_HEADERS] = allow_headers + if allow_methods: + cors_configuration[ALLOW_METHODS] = allow_methods + if expose_headers: + cors_configuration[EXPOSE_HEADERS] = expose_headers + if max_age is not None: + cors_configuration[MAX_AGE] = max_age + if allow_credentials is True: + cors_configuration[ALLOW_CREDENTIALS] = allow_credentials + + self._doc[self._X_APIGW_CORS] = cors_configuration + + def has_api_gateway_cors(self): + if self._doc.get(self._X_APIGW_CORS): + return True + return False + @property def openapi(self): """ @@ -355,6 +506,9 @@ def openapi(self): # Make sure any changes to the paths are reflected back in output self._doc["paths"] = self.paths + if self.tags: + self._doc["tags"] = self.tags + if self.security_schemes: self._doc.setdefault("components", {}) self._doc["components"]["securitySchemes"] = self.security_schemes diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index 8d8895e645..89da2c9647 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -162,7 +162,10 @@ def _add_api_to_swagger(self, event_id, event_properties, template): if isinstance(api_id, dict) or not template.get(api_id): raise InvalidEventException( event_id, - "RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource " "in same template", + self.api_id_property + + " must be a valid reference to an '" + + self._get_api_resource_type_name() + + "' resource in same template.", ) # Make sure Swagger is valid @@ -343,3 +346,11 @@ def _generate_implicit_api_resource(self): raise NotImplementedError( "Method _setup_api_properties() must be implemented in a " "subclass of ImplicitApiPlugin" ) + + def _get_api_resource_type_name(self): + """ + Returns the type of API resource + """ + raise NotImplementedError( + "Method _setup_api_properties() must be implemented in a " "subclass of ImplicitApiPlugin" + ) diff --git a/samtranslator/plugins/api/implicit_http_api_plugin.py b/samtranslator/plugins/api/implicit_http_api_plugin.py index 2e53190361..3f8849dbc1 100644 --- a/samtranslator/plugins/api/implicit_http_api_plugin.py +++ b/samtranslator/plugins/api/implicit_http_api_plugin.py @@ -1,5 +1,6 @@ import six +from samtranslator.model.intrinsics import make_conditional from samtranslator.model.naming import GeneratedLogicalId from samtranslator.plugins.api.implicit_api_plugin import ImplicitApiPlugin from samtranslator.public.open_api import OpenApiEditor @@ -88,6 +89,8 @@ def _process_api_events(self, function, api_events, template, condition=None): method_conditions[method] = condition self._add_api_to_swagger(logicalId, event_properties, template) + if "RouteSettings" in event_properties: + self._add_route_settings_to_api(logicalId, event_properties, template, condition) api_events[logicalId] = event # We could have made changes to the Events structure. Write it back to function @@ -116,6 +119,46 @@ def _get_api_definition_from_editor(self, editor): """ return editor.openapi + def _get_api_resource_type_name(self): + """ + Returns the type of API resource + """ + return "AWS::Serverless::HttpApi" + + def _add_route_settings_to_api(self, event_id, event_properties, template, condition): + """ + Adds the RouteSettings for this path/method from the given event to the RouteSettings configuration + on the AWS::Serverless::HttpApi that this refers to. + + :param string event_id: LogicalId of the event + :param dict event_properties: Properties of the event + :param SamTemplate template: SAM Template to search for Serverless::HttpApi resources + :param string condition: Condition on this HttpApi event (if any) + """ + + api_id = self._get_api_id(event_properties) + resource = template.get(api_id) + + path = event_properties["Path"] + method = event_properties["Method"] + + # Route should be in format "METHOD /path" or just "/path" if the ANY method is used + route = "{} {}".format(method.upper(), path) + if method == OpenApiEditor._X_ANY_METHOD: + route = path + + # Handle Resource-level conditions if necessary + api_route_settings = resource.properties.get("RouteSettings", {}) + event_route_settings = event_properties.get("RouteSettings", {}) + if condition: + event_route_settings = make_conditional(condition, event_properties.get("RouteSettings", {})) + + # Merge event-level and api-level RouteSettings properties + api_route_settings.setdefault(route, {}) + api_route_settings[route].update(event_route_settings) + resource.properties["RouteSettings"] = api_route_settings + template.set(api_id, resource) + class ImplicitHttpApiResource(SamResource): """ diff --git a/samtranslator/plugins/api/implicit_rest_api_plugin.py b/samtranslator/plugins/api/implicit_rest_api_plugin.py index ece4980fd0..3a6461928c 100644 --- a/samtranslator/plugins/api/implicit_rest_api_plugin.py +++ b/samtranslator/plugins/api/implicit_rest_api_plugin.py @@ -115,6 +115,12 @@ def _get_api_definition_from_editor(self, editor): """ return editor.swagger + def _get_api_resource_type_name(self): + """ + Returns the type of API resource + """ + return "AWS::Serverless::Api" + class ImplicitApiResource(SamResource): """ diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index 568620333f..c00df46fca 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -64,7 +64,17 @@ class Globals(object): "OpenApiVersion", "Domain", ], - SamResourceType.HttpApi.value: ["Auth", "AccessLogSettings", "StageVariables", "Tags"], + SamResourceType.HttpApi.value: [ + "Auth", + "AccessLogSettings", + "StageVariables", + "Tags", + "CorsConfiguration", + "DefaultRouteSettings", + "Domain", + "RouteSettings", + "FailOnWarnings", + ], SamResourceType.SimpleTable.value: ["SSESpecification"], } diff --git a/samtranslator/policy_templates_data/policy_templates.json b/samtranslator/policy_templates_data/policy_templates.json index 95e9f66a11..d14758caac 100644 --- a/samtranslator/policy_templates_data/policy_templates.json +++ b/samtranslator/policy_templates_data/policy_templates.json @@ -205,6 +205,48 @@ ] } }, + "DynamoDBWritePolicy": { + "Description": "Gives write only access to a DynamoDB Table", + "Parameters": { + "TableName": { + "Description": "Name of the DynamoDB Table" + } + }, + "Definition": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:BatchWriteItem" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}", + { + "tableName": { + "Ref": "TableName" + } + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/index/*", + { + "tableName": { + "Ref": "TableName" + } + } + ] + } + ] + } + ] + } + }, "DynamoDBReconfigurePolicy": { "Description": "Gives access reconfigure to a DynamoDB Table", "Parameters": { @@ -334,6 +376,48 @@ ] } }, + "S3WritePolicy": { + "Description": "Gives write permissions to objects in the S3 Bucket", + "Parameters": { + "BucketName": { + "Description": "Name of the Bucket" + } + }, + "Definition": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutLifecycleConfiguration" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}", + { + "bucketName": { + "Ref": "BucketName" + } + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}/*", + { + "bucketName": { + "Ref": "BucketName" + } + } + ] + } + ] + } + ] + } + }, "S3CrudPolicy": { "Description": "Gives CRUD permissions to objects in the S3 Bucket", "Parameters": { @@ -1904,6 +1988,32 @@ } ] } + }, + "EventBridgePutEventsPolicy": { + "Description": "Gives permissions to send events to EventBridge", + "Parameters": { + "EventBusName": { + "Description": "Name of the EventBridge EventBus" + } + }, + "Definition": { + "Statement": [ + { + "Effect": "Allow", + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": { + "Ref": "EventBusName" + } + } + ] + } + } + ] + } } } } diff --git a/samtranslator/public/intrinsics.py b/samtranslator/public/intrinsics.py index 5cbaab76f3..de40a31a66 100644 --- a/samtranslator/public/intrinsics.py +++ b/samtranslator/public/intrinsics.py @@ -2,4 +2,4 @@ from samtranslator.intrinsics.resolver import IntrinsicsResolver -from samtranslator.model.intrinsics import is_instrinsic as is_intrinsics +from samtranslator.model.intrinsics import is_intrinsic as is_intrinsics diff --git a/tests/model/api/test_http_api_generator.py b/tests/model/api/test_http_api_generator.py new file mode 100644 index 0000000000..c9656d0500 --- /dev/null +++ b/tests/model/api/test_http_api_generator.py @@ -0,0 +1,251 @@ +from unittest import TestCase +from mock import patch +import pytest +from functools import reduce + +from samtranslator.model import InvalidResourceException +from samtranslator.model.api.http_api_generator import HttpApiGenerator +from samtranslator.open_api.open_api import OpenApiEditor + + +class TestHttpApiGenerator(TestCase): + kwargs = { + "logical_id": "HttpApiId", + "stage_variables": None, + "depends_on": None, + "definition_body": None, + "definition_uri": None, + "stage_name": None, + "tags": None, + "auth": None, + "access_log_settings": None, + "resource_attributes": None, + "passthrough_resource_attributes": None, + } + authorizers = { + "Authorizers": { + "OAuth2": { + "AuthorizationScopes": ["scope"], + "JwtConfiguration": {"config": "value"}, + "IdentitySource": "https://example.com", + } + } + } + + def test_auth_no_def_body(self): + self.kwargs["auth"] = {"Authorizers": "configuration"} + self.kwargs["definition_body"] = None + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_wrong_properties(self): + self.kwargs["auth"] = {"Invalid": "auth"} + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_invalid_def_body(self): + self.kwargs["auth"] = {"Authorizers": "auth"} + self.kwargs["definition_body"] = {"Invalid": "open_api"} + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_invalid_auth_dict(self): + self.kwargs["auth"] = {"Authorizers": "auth"} + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_invalid_auth_strategy(self): + self.kwargs["auth"] = {"Authorizers": {"Auth1": "invalid"}} + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_missing_default_auth(self): + self.kwargs["auth"] = self.authorizers + self.kwargs["auth"]["DefaultAuthorizer"] = "DNE" + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_def_uri_invalid_dict(self): + self.kwargs["auth"] = None + self.kwargs["definition_body"] = None + self.kwargs["definition_uri"] = {"Invalid": "key"} + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_def_uri_invalid_uri(self): + self.kwargs["auth"] = None + self.kwargs["definition_body"] = None + self.kwargs["definition_uri"] = "invalid_uri" + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_no_def_body_or_uri(self): + self.kwargs["auth"] = None + self.kwargs["definition_body"] = None + self.kwargs["definition_uri"] = None + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + +class TestCustomDomains(TestCase): + kwargs = { + "logical_id": "HttpApiId", + "stage_variables": None, + "depends_on": None, + "definition_body": None, + "definition_uri": "s3://bucket/key", + "stage_name": None, + "tags": None, + "auth": None, + "access_log_settings": None, + "resource_attributes": None, + "passthrough_resource_attributes": None, + "domain": None, + } + + def test_no_domain(self): + self.kwargs["domain"] = None + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNone(domain) + self.assertIsNone(basepath) + self.assertIsNone(route) + + def test_no_domain_name(self): + self.kwargs["domain"] = {"CertificateArn": "someurl"} + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + with pytest.raises(InvalidResourceException) as e: + HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertEqual( + e.value.message, + "Resource with id [HttpApiId] is invalid. " + + "Custom Domains only works if both DomainName and CertificateArn are provided.", + ) + + def test_no_cert_arn(self): + self.kwargs["domain"] = {"DomainName": "example.com"} + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + with pytest.raises(InvalidResourceException) as e: + HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertEqual( + e.value.message, + "Resource with id [HttpApiId] is invalid. " + + "Custom Domains only works if both DomainName and CertificateArn are provided.", + ) + + def test_basic_domain_default_endpoint(self): + self.kwargs["domain"] = {"DomainName": "example.com", "CertificateArn": "some-url"} + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNotNone(domain, None) + self.assertIsNotNone(basepath, None) + self.assertEqual(len(basepath), 1) + self.assertIsNone(route, None) + self.assertEqual(domain.DomainNameConfigurations[0].get("EndpointType"), "REGIONAL") + + def test_basic_domain_regional_endpoint(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "EndpointConfiguration": "REGIONAL", + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNotNone(domain, None) + self.assertIsNotNone(basepath, None) + self.assertEqual(len(basepath), 1) + self.assertIsNone(route, None) + self.assertEqual(domain.DomainNameConfigurations[0].get("EndpointType"), "REGIONAL") + + def test_basic_domain_edge_endpoint(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "EndpointConfiguration": "EDGE", + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + with pytest.raises(InvalidResourceException) as e: + HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertEqual( + e.value.message, + "Resource with id [HttpApiId] is invalid. EndpointConfiguration for Custom Domains must be one of ['REGIONAL'].", + ) + + def test_bad_endpoint(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "EndpointConfiguration": "INVALID", + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + with pytest.raises(InvalidResourceException) as e: + HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertEqual( + e.value.message, + "Resource with id [HttpApiId] is invalid. " + + "EndpointConfiguration for Custom Domains must be one of ['REGIONAL'].", + ) + + def test_basic_route53(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "Route53": {"HostedZoneId": "xyz"}, + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNotNone(domain, None) + self.assertIsNotNone(basepath, None) + self.assertEqual(len(basepath), 1) + self.assertIsNotNone(route, None) + self.assertEqual(domain.DomainNameConfigurations[0].get("EndpointType"), "REGIONAL") + + def test_basepaths(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "BasePath": ["one", "two", "three"], + "Route53": {"HostedZoneId": "xyz"}, + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNotNone(domain, None) + self.assertIsNotNone(basepath, None) + self.assertEqual(len(basepath), 3) + self.assertIsNotNone(route, None) + self.assertEqual(domain.DomainNameConfigurations[0].get("EndpointType"), "REGIONAL") + + def test_invalid_basepaths(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "BasePath": ["inv*alid"], + "Route53": {"HostedZoneId": "xyz"}, + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + with pytest.raises(InvalidResourceException) as e: + HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertEqual( + e.value.message, "Resource with id [HttpApiId] is invalid. " + "Invalid Basepath name provided." + ) + + def test_basepaths(self): + self.kwargs["domain"] = { + "DomainName": "example.com", + "CertificateArn": "some-url", + "BasePath": ["one-1", "two_2", "three"], + "Route53": {"HostedZoneId": "xyz", "HostedZoneName": "abc", "IpV6": True}, + } + http_api = HttpApiGenerator(**self.kwargs)._construct_http_api() + domain, basepath, route = HttpApiGenerator(**self.kwargs)._construct_api_domain(http_api) + self.assertIsNotNone(domain, None) + self.assertIsNotNone(basepath, None) + self.assertEqual(len(basepath), 3) + self.assertIsNotNone(route, None) + self.assertEqual(route.HostedZoneName, None) + self.assertEqual(route.HostedZoneId, "xyz") + self.assertEqual(len(route.RecordSets), 2) diff --git a/tests/model/api/test_htttp_api_generator.py b/tests/model/api/test_htttp_api_generator.py deleted file mode 100644 index cee1621610..0000000000 --- a/tests/model/api/test_htttp_api_generator.py +++ /dev/null @@ -1,90 +0,0 @@ -from unittest import TestCase -from mock import patch -import pytest - -from samtranslator.model import InvalidResourceException -from samtranslator.model.api.http_api_generator import HttpApiGenerator -from samtranslator.open_api.open_api import OpenApiEditor - - -class TestHttpApiGenerator(TestCase): - kwargs = { - "logical_id": "HttpApiId", - "stage_variables": None, - "depends_on": None, - "definition_body": None, - "definition_uri": None, - "stage_name": None, - "tags": None, - "auth": None, - "access_log_settings": None, - "resource_attributes": None, - "passthrough_resource_attributes": None, - } - authorizers = { - "Authorizers": { - "OAuth2": { - "AuthorizationScopes": ["scope"], - "JwtConfiguration": {"config": "value"}, - "IdentitySource": "https://example.com", - } - } - } - - def test_auth_no_def_body(self): - self.kwargs["auth"] = {"Authorizers": "configuration"} - self.kwargs["definition_body"] = None - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_auth_wrong_properties(self): - self.kwargs["auth"] = {"Invalid": "auth"} - self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_auth_invalid_def_body(self): - self.kwargs["auth"] = {"Authorizers": "auth"} - self.kwargs["definition_body"] = {"Invalid": "open_api"} - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_auth_invalid_auth_dict(self): - self.kwargs["auth"] = {"Authorizers": "auth"} - self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_auth_invalid_auth_strategy(self): - self.kwargs["auth"] = {"Authorizers": {"Auth1": "invalid"}} - self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_auth_missing_default_auth(self): - self.kwargs["auth"] = self.authorizers - self.kwargs["auth"]["DefaultAuthorizer"] = "DNE" - self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_def_uri_invalid_dict(self): - self.kwargs["auth"] = None - self.kwargs["definition_body"] = None - self.kwargs["definition_uri"] = {"Invalid": "key"} - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_def_uri_invalid_uri(self): - self.kwargs["auth"] = None - self.kwargs["definition_body"] = None - self.kwargs["definition_uri"] = "invalid_uri" - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() - - def test_no_def_body_or_uri(self): - self.kwargs["auth"] = None - self.kwargs["definition_body"] = None - self.kwargs["definition_uri"] = None - with pytest.raises(InvalidResourceException): - HttpApiGenerator(**self.kwargs)._construct_http_api() diff --git a/tests/model/test_api_v2.py b/tests/model/test_api_v2.py index 36d87b5e1e..e9fe87d934 100644 --- a/tests/model/test_api_v2.py +++ b/tests/model/test_api_v2.py @@ -15,16 +15,6 @@ def test_create_oauth2_auth(self): ) self.assertEquals(auth.auth_type, "oauth2") - def test_create_oidc_auth(self): - auth = ApiGatewayV2Authorizer( - api_logical_id="logicalId", - name="authName", - open_id_connect_url="https://example.com", - jwt_configuration={"config": "value"}, - id_source="https://example.com", - ) - self.assertEquals(auth.auth_type, "openIdConnect") - def test_create_authorizer_no_id_source(self): with pytest.raises(InvalidResourceException): auth = ApiGatewayV2Authorizer( diff --git a/tests/model/test_function_policies.py b/tests/model/test_function_policies.py index 4e39d05814..c7d9d90414 100644 --- a/tests/model/test_function_policies.py +++ b/tests/model/test_function_policies.py @@ -85,7 +85,7 @@ def test_get_type_must_work_for_managed_policy(self): result = self.function_policies._get_type(policy) self.assertEqual(result, expected) - @patch("samtranslator.model.function_policies.is_instrinsic") + @patch("samtranslator.model.function_policies.is_intrinsic") def test_get_type_must_work_for_managed_policy_with_intrinsics(self, is_intrinsic_mock): policy = {"Ref": "somevalue"} expected = PolicyTypes.MANAGED_POLICY diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index 95eceb135a..a70227e358 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -217,7 +217,7 @@ def test_must_add_new_integration_to_new_path(self): _X_INTEGRATION: { "type": "aws_proxy", "httpMethod": "POST", - "payloadFormatVersion": "1.0", + "payloadFormatVersion": "2.0", "uri": integration_uri, }, } @@ -241,7 +241,7 @@ def test_must_add_new_integration_with_conditions_to_new_path(self): _X_INTEGRATION: { "type": "aws_proxy", "httpMethod": "POST", - "payloadFormatVersion": "1.0", + "payloadFormatVersion": "2.0", "uri": {"Fn::If": ["condition", integration_uri, {"Ref": "AWS::NoValue"}]}, }, }, @@ -268,7 +268,7 @@ def test_must_add_new_integration_to_existing_path(self): _X_INTEGRATION: { "type": "aws_proxy", "httpMethod": "POST", - "payloadFormatVersion": "1.0", + "payloadFormatVersion": "2.0", "uri": integration_uri, }, } @@ -353,7 +353,6 @@ def test_must_fail_for_invalid_values(self, data, case): self.assertFalse(OpenApiEditor.is_valid(data), "openapi dictionary with {} must not be valid".format(case)) -# TODO this needs to be updated with OIDC auth - authorization scopes and anything else that needs testing the swagger class TestOpenApiEditor_add_auth(TestCase): def setUp(self): diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index bb21d08b52..a4325c85a9 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -1015,7 +1015,7 @@ def test_must_add_fn_if_custom_statements(self): "condition", {"Action": "execute-api:Invoke", "Resource": ["execute-api:/*/*/*"]}, {"Action": "execute-api:blah", "Resource": ["execute-api:/*/*/*"]}, - ], + ] } } diff --git a/tests/test_intrinsics.py b/tests/test_intrinsics.py index af094f7e2d..5a3f22f4f5 100644 --- a/tests/test_intrinsics.py +++ b/tests/test_intrinsics.py @@ -1,7 +1,7 @@ from parameterized import parameterized from unittest import TestCase -from samtranslator.model.intrinsics import is_instrinsic, make_shorthand +from samtranslator.model.intrinsics import is_intrinsic, make_shorthand class TestIntrinsics(TestCase): @@ -10,16 +10,16 @@ def test_is_intrinsic_must_detect_intrinsics(self, intrinsic_name): input = {intrinsic_name: ["some value"]} - self.assertTrue(is_instrinsic(input)) + self.assertTrue(is_intrinsic(input)) def test_is_intrinsic_on_empty_input(self): - self.assertFalse(is_instrinsic(None)) + self.assertFalse(is_intrinsic(None)) def test_is_intrinsic_on_non_dict_input(self): - self.assertFalse(is_instrinsic([1, 2, 3])) + self.assertFalse(is_intrinsic([1, 2, 3])) def test_is_intrinsic_on_intrinsic_like_dict_input(self): - self.assertFalse(is_instrinsic({"Ref": "foo", "key": "bar"})) + self.assertFalse(is_intrinsic({"Ref": "foo", "key": "bar"})) @parameterized.expand([({"Ref": "foo"}, "${foo}"), ({"Fn::GetAtt": ["foo", "Arn"]}, "${foo.Arn}")]) def test_make_shorthand_success(self, input, expected): diff --git a/tests/translator/input/all_policy_templates.yaml b/tests/translator/input/all_policy_templates.yaml index cab96b6b93..5216a65f2c 100644 --- a/tests/translator/input/all_policy_templates.yaml +++ b/tests/translator/input/all_policy_templates.yaml @@ -159,3 +159,12 @@ Resources: - AthenaQueryPolicy: WorkGroupName: name + + - S3WritePolicy: + BucketName: name + + - DynamoDBWritePolicy: + TableName: name + + - EventBridgePutEventsPolicy: + EventBusName: name diff --git a/tests/translator/input/api_with_basic_custom_domain_http.yaml b/tests/translator/input/api_with_basic_custom_domain_http.yaml new file mode 100644 index 0000000000..e9a0c5f8a7 --- /dev/null +++ b/tests/translator/input/api_with_basic_custom_domain_http.yaml @@ -0,0 +1,50 @@ +Parameters: + MyDomainName: + Type: String + Default: sam-example.com + + MyDomainCert: + Type: String + Default: arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3 + +Globals: + HttpApi: + Domain: + DomainName: !Ref MyDomainName + CertificateArn: !Ref MyDomainCert + EndpointConfiguration: REGIONAL + BasePath: ["/basic", "/begin-here"] + Route53: + HostedZoneName: sam-example.com. + + +Resources: + HttpApiFunction: + 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: + Basic: + Type: HttpApi + Properties: + Path: /basic + Method: post + ApiId: !Ref MyApi + SimpleCase: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod \ No newline at end of file diff --git a/tests/translator/input/api_with_basic_custom_domain_intrinsics_http.yaml b/tests/translator/input/api_with_basic_custom_domain_intrinsics_http.yaml new file mode 100644 index 0000000000..03cb1323cd --- /dev/null +++ b/tests/translator/input/api_with_basic_custom_domain_intrinsics_http.yaml @@ -0,0 +1,53 @@ +Conditions: + C1: + Fn::Equals: + - true + - true +Parameters: + MyDomainCert: + Type: String + Default: another-api-arn + + EndpointConf: + Type: String + Default: REGIONAL + +Resources: + MyFunction: + Condition: C1 + 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: + Api: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + Method: Put + Path: /get + ImplicitGet: + Type: HttpApi + Properties: + Method: Post + Path: /implicit + + MyApi: + Condition: C1 + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod + Domain: + DomainName: !Sub 'example-${AWS::Region}.com' + CertificateArn: !Ref MyDomainCert + EndpointConfiguration: !Ref EndpointConf + BasePath: [ "/get", "/fetch" ] + diff --git a/tests/translator/input/api_with_custom_domain_route53_hosted_zone_name_http.yaml b/tests/translator/input/api_with_custom_domain_route53_hosted_zone_name_http.yaml new file mode 100644 index 0000000000..9fbc632a89 --- /dev/null +++ b/tests/translator/input/api_with_custom_domain_route53_hosted_zone_name_http.yaml @@ -0,0 +1,43 @@ +Parameters: + DomainName: + Type: String + Default: 'example.com' + ACMCertificateArn: + Type: String + Default: 'cert-arn-in-us-east-1' +Globals: + HttpApi: + Domain: + DomainName: !Ref DomainName + CertificateArn: !Ref ACMCertificateArn + BasePath: + - /one + Route53: + HostedZoneName: www.my-domain.com. + IpV6: false +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: HttpApi + Properties: + ApiId: !Ref MyApi + Method: Post + Path: /fetch + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod diff --git a/tests/translator/input/api_with_custom_domain_route53_http.yaml b/tests/translator/input/api_with_custom_domain_route53_http.yaml new file mode 100644 index 0000000000..f3e91b05d2 --- /dev/null +++ b/tests/translator/input/api_with_custom_domain_route53_http.yaml @@ -0,0 +1,41 @@ +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: HttpApi + Properties: + ApiId: !Ref MyApi + Method: Post + Path: /fetch + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod + Domain: + DomainName: !Ref DomainName + CertificateArn: !Ref ACMCertificateArn + BasePath: + - /one + Route53: + HostedZoneId: ZQ1UAL4EFZVME + IpV6: true \ No newline at end of file diff --git a/tests/translator/input/api_with_usageplans.yaml b/tests/translator/input/api_with_usageplans.yaml index 36aa0ba0a5..836d98648b 100644 --- a/tests/translator/input/api_with_usageplans.yaml +++ b/tests/translator/input/api_with_usageplans.yaml @@ -126,3 +126,15 @@ Outputs: Description: "API endpoint URL for Prod environment" Value: Fn::Sub: 'https://${MyApiThree}.execute-api.${AWS::Region}.amazonaws.com/Prod/' + UsagePlan: + Description: "Usage Plan physical Id" + Value: + !Ref MyApiTwo.UsagePlan + UsagePlanKey: + Description: "Usage Plan Key" + Value: + !Ref MyApiTwo.UsagePlanKey + ApiKey: + Description: "Api Key" + Value: + !Ref MyApiThree.ApiKey \ No newline at end of file diff --git a/tests/translator/input/error_http_api_invalid_auth.yaml b/tests/translator/input/error_http_api_invalid_auth.yaml index 76f24464be..a45f748002 100644 --- a/tests/translator/input/error_http_api_invalid_auth.yaml +++ b/tests/translator/input/error_http_api_invalid_auth.yaml @@ -53,6 +53,20 @@ Resources: Authorizer: OAuth2 AuthorizationScopes: "scope" + Function5: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.7 + Handler: index.handler + CodeUri: s3://bucket/key + Events: + Api4: + Type: HttpApi + Properties: + ApiId: !Ref MyApi5 + Auth: + Authorizer: OIDC + MyApi: Type: AWS::Serverless::HttpApi Properties: @@ -120,6 +134,28 @@ Resources: audience: - MyApi IdentitySource: "$request.querystring.param" + DefinitionBody: + info: + version: '1.0' + title: + Ref: AWS::StackName + paths: {} + openapi: 3.0.1 + + MyApi5: + Type: AWS::Serverless::HttpApi + Properties: + Auth: + Authorizers: + OIDC: + OpenIdConnectUrl: "https://example.com/url" + AuthorizationScopes: + - scope4 + JwtConfiguration: + issuer: "https://www.example.com/v1/connect/oidc" + audience: + - MyApi + IdentitySource: "$request.querystring.param" DefinitionBody: info: version: '1.0' diff --git a/tests/translator/input/error_http_api_tags.yaml b/tests/translator/input/error_http_api_tags.yaml new file mode 100644 index 0000000000..2af7574b4b --- /dev/null +++ b/tests/translator/input/error_http_api_tags.yaml @@ -0,0 +1,8 @@ +Resources: + Api: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionBody: + invalid: def_body + Tags: + Tag: value \ No newline at end of file diff --git a/tests/translator/input/error_http_api_tags_def_uri.yaml b/tests/translator/input/error_http_api_tags_def_uri.yaml new file mode 100644 index 0000000000..dfe4cb642b --- /dev/null +++ b/tests/translator/input/error_http_api_tags_def_uri.yaml @@ -0,0 +1,7 @@ +Resources: + Api: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionUri: s3://bucket/key + Tags: + Tag: value \ No newline at end of file diff --git a/tests/translator/input/error_http_api_with_cors_def_uri.yaml b/tests/translator/input/error_http_api_with_cors_def_uri.yaml new file mode 100644 index 0000000000..37b93f5d96 --- /dev/null +++ b/tests/translator/input/error_http_api_with_cors_def_uri.yaml @@ -0,0 +1,22 @@ +Resources: + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + CorsConfiguration: + AllowCredentials: false + DefinitionUri: s3://bucket/key + Tags: + Tag: value + StageName: !Join ["", ["Stage", "Name"]] + + Function: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.7 + Handler: index.handler + CodeUri: s3://bucket/key + Events: + Api: + Type: HttpApi + Properties: + ApiId: MyApi \ No newline at end of file diff --git a/tests/translator/input/explicit_http_api.yaml b/tests/translator/input/explicit_http_api.yaml index afab76416e..2d50630512 100644 --- a/tests/translator/input/explicit_http_api.yaml +++ b/tests/translator/input/explicit_http_api.yaml @@ -10,13 +10,16 @@ Resources: Type: HttpApi Properties: ApiId: !Ref MyApi - SimpleCase2: + BasePath: Type: HttpApi Properties: ApiId: !Ref MyApi2 + Path: / + Method: get MyApi: Type: AWS::Serverless::HttpApi Properties: + FailOnWarnings: True Auth: Authorizers: OAuth2: @@ -40,4 +43,20 @@ Resources: audience: - MyApi IdentitySource: "$request.querystring.param" - DefaultAuthorizer: OAuth2 \ No newline at end of file + DefaultAuthorizer: OAuth2 + DefinitionBody: + openapi: '3.0' + info: + title: !Sub ${AWS::StackName}-Apiv2 + paths: + /: + get: + responses: {} + $default: + x-amazon-apigateway-any-method: + isDefaultRoute: true + x-amazon-apigateway-integration: + httpMethod: ANY + type: http_proxy + uri: https://www.alphavantage.co/ + payloadFormatVersion: '1.0' \ No newline at end of file diff --git a/tests/translator/input/http_api_def_uri.yaml b/tests/translator/input/http_api_def_uri.yaml index 4e6c80316d..0182af3940 100644 --- a/tests/translator/input/http_api_def_uri.yaml +++ b/tests/translator/input/http_api_def_uri.yaml @@ -1,11 +1,16 @@ +Globals: + HttpApi: + DefaultRouteSettings: + ThrottlingRateLimit: 100.0 + Resources: MyApi: Type: AWS::Serverless::HttpApi Properties: DefinitionUri: s3://bucket/key - Tags: - Tag: value StageName: !Join ["", ["Stage", "Name"]] + DefaultRouteSettings: + ThrottlingBurstLimit: 50 MyApi2: Type: AWS::Serverless::HttpApi @@ -14,8 +19,6 @@ Resources: Bucket: bucket Key: key Version: version - Tags: - Tag: value Function: Type: AWS::Serverless::Function diff --git a/tests/translator/input/http_api_existing_openapi.yaml b/tests/translator/input/http_api_existing_openapi.yaml index de5a34d927..891d9199e9 100644 --- a/tests/translator/input/http_api_existing_openapi.yaml +++ b/tests/translator/input/http_api_existing_openapi.yaml @@ -1,5 +1,21 @@ +Parameters: + Timeout: + Default: 15000 + Type: Number + +Globals: + HttpApi: + CorsConfiguration: + AllowHeaders: + - x-apigateway-header + AllowMethods: + - GET + AllowOrigins: + - https://global.com + MaxAge: 6000 + Resources: - HttpApiFunction: + HttpApiFunction: Type: AWS::Serverless::Function Properties: CodeUri: s3://sam-demo-bucket/todo_list.zip @@ -13,18 +29,36 @@ Resources: Path: /basic Method: post ApiId: !Ref MyApi + TimeoutInMillis: 10000 SimpleCase: # path exists, integration doesn't Type: HttpApi Properties: ApiId: !Ref MyApi + TimeoutInMillis: !Ref Timeout + PayloadFormatVersion: '1.0' + PathParametersExisting: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + Path: /get/{something}/with/{params} + Method: GET MyApi: Type: AWS::Serverless::HttpApi Properties: + CorsConfiguration: + AllowOrigins: + - "https://local.com" DefinitionBody: info: version: '1.0' title: Ref: AWS::StackName + x-amazon-apigateway-cors: + allowOrigins: + - "https://www.overriden.com" + allowMethods: + - "POST" + maxAge: 3600 paths: "/basic": post: @@ -38,6 +72,13 @@ Resources: - OpenIdAuth: - scope3 responses: {} + "/get/{something}/with/{params}": + get: + parameters: + - + name: something + in: path + responses: {} "/integration": post: x-amazon-apigateway-integration: diff --git a/tests/translator/input/http_api_existing_openapi_conditions.yaml b/tests/translator/input/http_api_existing_openapi_conditions.yaml index 7a42bd4580..6b9c91c5f2 100644 --- a/tests/translator/input/http_api_existing_openapi_conditions.yaml +++ b/tests/translator/input/http_api_existing_openapi_conditions.yaml @@ -15,6 +15,7 @@ Resources: Basic: # integration exists Type: HttpApi Properties: + PayloadFormatVersion: "2.0" Path: /basic Method: post ApiId: !Ref MyApi @@ -35,6 +36,9 @@ Resources: MyApi: Type: AWS::Serverless::HttpApi Properties: + Tags: + Tag1: value1 + Tag2: value2 Auth: Authorizers: OAuth2: @@ -106,6 +110,9 @@ Resources: - scope4 responses: {} openapi: 3.0.1 + tags: + - name: Tag1 + description: this tag exists, but doesn't have an amazon extension value components: securitySchemes: oauth2Auth: diff --git a/tests/translator/input/http_api_explicit_stage.yaml b/tests/translator/input/http_api_explicit_stage.yaml index 907faec42a..576ab3a6a7 100644 --- a/tests/translator/input/http_api_explicit_stage.yaml +++ b/tests/translator/input/http_api_explicit_stage.yaml @@ -1,3 +1,18 @@ +Parameters: + CorsParam: + Type: String + Default: True + PayloadFormatVersion: + Default: '1.0' + Type: String + +Globals: + HttpApi: + RouteSettings: + "$default": + DataTraceEnabled: True + ThrottlingBurstLimit: 100 + FailOnWarnings: true Resources: HttpApiFunction: Type: AWS::Serverless::Function @@ -10,7 +25,22 @@ Resources: Type: HttpApi Properties: ApiId: !Ref MyApi + RouteSettings: + ThrottlingBurstLimit: 300 + LoggingLevel: INFO + PayloadFormatVersion: !Ref PayloadFormatVersion + MyApi: Type: AWS::Serverless::HttpApi Properties: - StageName: Prod \ No newline at end of file + StageName: Prod + StageVariables: + VarName: VarValue + RouteSettings: + "$default": + ThrottlingBurstLimit: 200 + ThrottlingRateLimit: 0.7 + AccessLogSettings: + DestinationArn: arn:aws:logs:us-east-1:123456789012:log-group:LogGroupName + Format: $context.requestId + CorsConfiguration: !Ref CorsParam \ No newline at end of file diff --git a/tests/translator/input/http_api_with_cors.yaml b/tests/translator/input/http_api_with_cors.yaml new file mode 100644 index 0000000000..0c0ebee49c --- /dev/null +++ b/tests/translator/input/http_api_with_cors.yaml @@ -0,0 +1,57 @@ +Conditions: + C1: + Fn::Equals: + - true + - true + +Globals: + HttpApi: + CorsConfiguration: + Fn::If: + - C1 + - AllowHeaders: + - x-apigateway-header + AllowMethods: + - GET + AllowOrigins: + - https://foo.com + ExposeHeaders: + - x-amzn-header + - AWS::NoValue + +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + console.log("Hello from MyAuthFunction") + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Handler: index.handler + Runtime: nodejs12.x + Events: + SimpleCase: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + ImplicitApi: + Type: HttpApi + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionBody: + info: + version: '1.0' + title: + Ref: AWS::StackName + paths: + "$default": + x-amazon-apigateway-any-method: + isDefaultRoute: true + openapi: 3.0.1 \ No newline at end of file diff --git a/tests/translator/input/implicit_http_api.yaml b/tests/translator/input/implicit_http_api.yaml index 536e3f2661..ee141d7d55 100644 --- a/tests/translator/input/implicit_http_api.yaml +++ b/tests/translator/input/implicit_http_api.yaml @@ -28,3 +28,8 @@ Resources: Properties: Path: /basic2 Method: post + PathParameters: + Type: HttpApi + Properties: + Path: /get/{something}/with/{params} + Method: POST diff --git a/tests/translator/input/implicit_http_api_auth_and_simple_case.yaml b/tests/translator/input/implicit_http_api_auth_and_simple_case.yaml index ff37c4daab..944e7c8400 100644 --- a/tests/translator/input/implicit_http_api_auth_and_simple_case.yaml +++ b/tests/translator/input/implicit_http_api_auth_and_simple_case.yaml @@ -27,13 +27,6 @@ Resources: Auth: AuthorizationScopes: - scope3 - SomeAuth: - Type: HttpApi - Properties: - Path: /someauth - Method: post - Auth: - Authorizer: OpenIdAuth oauth2Path: Type: HttpApi Properties: @@ -48,16 +41,6 @@ Globals: HttpApi: Auth: Authorizers: - OpenIdAuth: - AuthorizationScopes: - - scope1 - - scope2 - OpenIdConnectUrl: "https://www.example.com/v1/connect" - JwtConfiguration: - issuer: "https://www.example.com/v1/connect/oidc" - audience: - - MyApi - IdentitySource: "$request.querystring.param" oauth2Auth: AuthorizationScopes: - scope4 @@ -66,4 +49,4 @@ Globals: audience: - MyApi IdentitySource: "$request.querystring.param" - DefaultAuthorizer: OpenIdAuth + DefaultAuthorizer: oauth2Auth diff --git a/tests/translator/input/implicit_http_api_with_many_conditions.yaml b/tests/translator/input/implicit_http_api_with_many_conditions.yaml index 2fcda10e25..47dad4bd63 100644 --- a/tests/translator/input/implicit_http_api_with_many_conditions.yaml +++ b/tests/translator/input/implicit_http_api_with_many_conditions.yaml @@ -48,6 +48,9 @@ Conditions: - false Globals: HttpApi: + RouteSettings: + "GET /sub": + ThrottlingBurstLimit: 100 Auth: Authorizers: oauth2: @@ -73,6 +76,8 @@ Resources: HttpApiEvent: Type: HttpApi Properties: + RouteSettings: + ThrottlingBurstLimit: 200 Path: /sub Method: get helloworld1099: @@ -89,6 +94,8 @@ Resources: HttpApiEvent: Type: HttpApi Properties: + RouteSettings: + ThrottlingBurstLimit: 200 Auth: Authorizer: oauth2 HttpApiEvent2: diff --git a/tests/translator/output/all_policy_templates.json b/tests/translator/output/all_policy_templates.json index 7ec44c72df..f98f0fbc16 100644 --- a/tests/translator/output/all_policy_templates.json +++ b/tests/translator/output/all_policy_templates.json @@ -1485,6 +1485,91 @@ } ] } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy55", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutLifecycleConfiguration" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}", + { + "bucketName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}/*", + { + "bucketName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy56", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:BatchWriteItem" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}", + { + "tableName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/index/*", + { + "tableName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy57", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "name" + } + ] + }, + "Effect": "Allow" + } + ] + } } ], "Tags": [ diff --git a/tests/translator/output/api_with_basic_custom_domain_http.json b/tests/translator/output/api_with_basic_custom_domain_http.json new file mode 100644 index 0000000000..f9bb82d27a --- /dev/null +++ b/tests/translator/output/api_with_basic_custom_domain_http.json @@ -0,0 +1,220 @@ +{ + "Parameters": { + "MyDomainName": { + "Default": "sam-example.com", + "Type": "String" + }, + "MyDomainCert": { + "Default": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "Type": "String" + } + }, + "Resources": { + "MyApibasicApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "basic", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV22dbf35af54": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "sam-example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "HttpApiFunction": { + "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": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApibeginhereApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "begin-here", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "HttpApiFunctionRole": { + "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" + } + ] + } + }, + "RecordSetGroup1f4f569a7e": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "sam-example.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "sam-example.com" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_basic_custom_domain_intrinsics_http.json b/tests/translator/output/api_with_basic_custom_domain_intrinsics_http.json new file mode 100644 index 0000000000..dca8fe2e79 --- /dev/null +++ b/tests/translator/output/api_with_basic_custom_domain_intrinsics_http.json @@ -0,0 +1,318 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Parameters": { + "MyDomainCert": { + "Default": "another-api-arn", + "Type": "String" + }, + "EndpointConf": { + "Default": "REGIONAL", + "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" + } + ] + }, + "Condition": "C1" + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/implicit": { + "Fn::If": [ + "C1", + { + "post": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyFunctionImplicitGetPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/implicit", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "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" + } + ] + }, + "Condition": "C1" + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/get": { + "Fn::If": [ + "C1", + { + "put": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyFunctionApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/get", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "MyApigetApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV2c0ed6fae34" + }, + "ApiMappingKey": "get", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + }, + "ApiGatewayDomainNameV2c0ed6fae34": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": { + "Fn::Sub": "example-ap-southeast-1.com" + }, + "DomainNameConfigurations": [ + { + "CertificateArn": "another-api-arn", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyApifetchApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV2c0ed6fae34" + }, + "ApiMappingKey": "fetch", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_custom_domain_route53_hosted_zone_name_http.json b/tests/translator/output/api_with_custom_domain_route53_hosted_zone_name_http.json new file mode 100644 index 0000000000..d9ffc5aac4 --- /dev/null +++ b/tests/translator/output/api_with_custom_domain_route53_hosted_zone_name_http.json @@ -0,0 +1,191 @@ +{ + "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" + } + ] + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "RecordSetGroup456ebaf280": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "www.my-domain.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + } + ] + } + }, + "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" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_custom_domain_route53_http.json b/tests/translator/output/api_with_custom_domain_route53_http.json new file mode 100644 index 0000000000..54e445611c --- /dev/null +++ b/tests/translator/output/api_with_custom_domain_route53_http.json @@ -0,0 +1,209 @@ +{ + "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" + } + ] + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "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" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_usageplans.json b/tests/translator/output/api_with_usageplans.json index 6e11a953af..4885484657 100644 --- a/tests/translator/output/api_with_usageplans.json +++ b/tests/translator/output/api_with_usageplans.json @@ -17,6 +17,24 @@ "Value": { "Fn::Sub": "https://${MyApiOne}.execute-api.${AWS::Region}.amazonaws.com/Prod/" } + }, + "ApiKey": { + "Description": "Api Key", + "Value": { + "Ref": "ServerlessApiKey" + } + }, + "UsagePlanKey": { + "Description": "Usage Plan Key", + "Value": { + "Ref": "MyApiTwoUsagePlanKey" + } + }, + "UsagePlan": { + "Description": "Usage Plan physical Id", + "Value": { + "Ref": "MyApiTwoUsagePlan" + } } }, "Resources": { diff --git a/tests/translator/output/aws-cn/all_policy_templates.json b/tests/translator/output/aws-cn/all_policy_templates.json index 07e1b6785c..fdd861115b 100644 --- a/tests/translator/output/aws-cn/all_policy_templates.json +++ b/tests/translator/output/aws-cn/all_policy_templates.json @@ -1485,6 +1485,91 @@ } ] } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy55", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutLifecycleConfiguration" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}", + { + "bucketName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}/*", + { + "bucketName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy56", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:BatchWriteItem" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}", + { + "tableName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/index/*", + { + "tableName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy57", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "name" + } + ] + }, + "Effect": "Allow" + } + ] + } } ], "Tags": [ diff --git a/tests/translator/output/aws-cn/api_with_basic_custom_domain_http.json b/tests/translator/output/aws-cn/api_with_basic_custom_domain_http.json new file mode 100644 index 0000000000..08b0bea500 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_basic_custom_domain_http.json @@ -0,0 +1,220 @@ +{ + "Parameters": { + "MyDomainName": { + "Default": "sam-example.com", + "Type": "String" + }, + "MyDomainCert": { + "Default": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "Type": "String" + } + }, + "Resources": { + "MyApibasicApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "basic", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV22dbf35af54": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "sam-example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "HttpApiFunction": { + "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": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApibeginhereApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "begin-here", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "RecordSetGroup1f4f569a7e": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "sam-example.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "sam-example.com" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics_http.json b/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics_http.json new file mode 100644 index 0000000000..02d0cb9760 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_basic_custom_domain_intrinsics_http.json @@ -0,0 +1,318 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Parameters": { + "MyDomainCert": { + "Default": "another-api-arn", + "Type": "String" + }, + "EndpointConf": { + "Default": "REGIONAL", + "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" + } + ] + }, + "Condition": "C1" + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/implicit": { + "Fn::If": [ + "C1", + { + "post": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyFunctionImplicitGetPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/implicit", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "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-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "C1" + }, + "ApiGatewayDomainNameV2c0cd2d9dfc": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainNameConfigurations": [ + { + "CertificateArn": "another-api-arn", + "EndpointType": "REGIONAL" + } + ], + "DomainName": { + "Fn::Sub": "example-cn-north-1.com" + }, + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyFunctionApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/get", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "MyApigetApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV2c0cd2d9dfc" + }, + "ApiMappingKey": "get", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/get": { + "Fn::If": [ + "C1", + { + "put": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyApifetchApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV2c0cd2d9dfc" + }, + "ApiMappingKey": "fetch", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_custom_domain_route53_hosted_zone_name_http.json b/tests/translator/output/aws-cn/api_with_custom_domain_route53_hosted_zone_name_http.json new file mode 100644 index 0000000000..351eeedb83 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_custom_domain_route53_hosted_zone_name_http.json @@ -0,0 +1,191 @@ +{ + "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" + } + ] + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "RecordSetGroup456ebaf280": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "www.my-domain.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + } + ] + } + }, + "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-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_custom_domain_route53_http.json b/tests/translator/output/aws-cn/api_with_custom_domain_route53_http.json new file mode 100644 index 0000000000..34e7a8e9df --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_custom_domain_route53_http.json @@ -0,0 +1,209 @@ +{ + "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" + } + ] + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "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-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_usageplans.json b/tests/translator/output/aws-cn/api_with_usageplans.json index 7f3fe62693..60dc7b392b 100644 --- a/tests/translator/output/aws-cn/api_with_usageplans.json +++ b/tests/translator/output/aws-cn/api_with_usageplans.json @@ -17,6 +17,24 @@ "Value": { "Fn::Sub": "https://${MyApiOne}.execute-api.${AWS::Region}.amazonaws.com/Prod/" } + }, + "ApiKey": { + "Description": "Api Key", + "Value": { + "Ref": "ServerlessApiKey" + } + }, + "UsagePlanKey": { + "Description": "Usage Plan Key", + "Value": { + "Ref": "MyApiTwoUsagePlanKey" + } + }, + "UsagePlan": { + "Description": "Usage Plan physical Id", + "Value": { + "Ref": "MyApiTwoUsagePlan" + } } }, "Resources": { diff --git a/tests/translator/output/aws-cn/explicit_http_api.json b/tests/translator/output/aws-cn/explicit_http_api.json index d29519d7d0..2eebb7bb86 100644 --- a/tests/translator/output/aws-cn/explicit_http_api.json +++ b/tests/translator/output/aws-cn/explicit_http_api.json @@ -7,7 +7,10 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "MyApi2ApiGatewayDefaultStage": { @@ -17,7 +20,10 @@ "Ref": "MyApi2" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunction": { @@ -69,23 +75,37 @@ "Properties": { "Body": { "info": { - "version": "1.0", "title": { - "Ref": "AWS::StackName" + "Fn::Sub": "${AWS::StackName}-Apiv2" } }, "paths": { "$default": { "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "isDefaultRoute": true, + "security": [ + { + "OAuth2": [] + } + ] + } + }, + "/": { + "get": { "x-amazon-apigateway-integration": { "httpMethod": "POST", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, - "isDefaultRoute": true, "security": [ { "OAuth2": [] @@ -95,6 +115,7 @@ } } }, + "openapi": "3.0", "components": { "securitySchemes": { "OAuth2": { @@ -112,7 +133,12 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } }, @@ -146,7 +172,7 @@ ] } }, - "HttpApiFunctionSimpleCase2Permission": { + "HttpApiFunctionBasePathPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -156,7 +182,7 @@ }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", { "__Stage__": "*", "__ApiId__": { @@ -170,6 +196,7 @@ "MyApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { "version": "1.0", @@ -186,7 +213,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ @@ -200,6 +227,7 @@ } } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "OAuth2": { @@ -217,9 +245,14 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/explicit_http_api_minimum.json b/tests/translator/output/aws-cn/explicit_http_api_minimum.json index 992ca5675d..09367ab43e 100644 --- a/tests/translator/output/aws-cn/explicit_http_api_minimum.json +++ b/tests/translator/output/aws-cn/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -108,7 +120,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} @@ -126,7 +138,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "ApiApiGatewayDefaultStage": { @@ -136,7 +151,10 @@ "Ref": "Api" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } } } diff --git a/tests/translator/output/aws-cn/function_with_event_dest_basic.json b/tests/translator/output/aws-cn/function_with_event_dest_basic.json index a21943243f..01a3440e10 100644 --- a/tests/translator/output/aws-cn/function_with_event_dest_basic.json +++ b/tests/translator/output/aws-cn/function_with_event_dest_basic.json @@ -121,21 +121,12 @@ "Runtime": "nodejs12.x" } }, - "MyTestFunctionVersion948f7f815d": { - "DeletionPolicy": "Retain", - "Type": "AWS::Lambda::Version", - "Properties": { - "FunctionName": { - "Ref": "MyTestFunction" - } - } - }, "MyTestFunctionAliaslive": { "Type": "AWS::Lambda::Alias", "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MyTestFunctionVersion948f7f815d", + "MyTestFunctionVersiondaf9da458d", "Version" ] }, @@ -144,6 +135,15 @@ }, "Name": "live" } + }, + "MyTestFunctionVersiondaf9da458d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } } } } \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_event_source_mapping.json b/tests/translator/output/aws-cn/function_with_event_source_mapping.json index 9d0a7a7868..b33219b62c 100644 --- a/tests/translator/output/aws-cn/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-cn/function_with_event_source_mapping.json @@ -169,8 +169,7 @@ "MySqsQueue", "Arn" ] - }, - "Type": "SQS" + } } }, "EventSourceArn": { @@ -227,8 +226,7 @@ "OnFailure": { "Destination": { "Ref": "MySnsTopic" - }, - "Type": "SNS" + } } } } diff --git a/tests/translator/output/aws-cn/globals_for_function.json b/tests/translator/output/aws-cn/globals_for_function.json index 0724bd4e87..148dfb9c66 100644 --- a/tests/translator/output/aws-cn/globals_for_function.json +++ b/tests/translator/output/aws-cn/globals_for_function.json @@ -144,7 +144,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "FunctionWithOverridesVersion640128d35d", + "FunctionWithOverridesVersion096ed3b52b", "Version" ] }, @@ -211,7 +211,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MinimalFunctionVersionfb7aeaa544", + "MinimalFunctionVersion0a06fc8fb1", "Version" ] }, @@ -221,7 +221,7 @@ "Name": "live" } }, - "FunctionWithOverridesVersion640128d35d": { + "FunctionWithOverridesVersion096ed3b52b": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { @@ -230,7 +230,7 @@ } } }, - "MinimalFunctionVersionfb7aeaa544": { + "MinimalFunctionVersion0a06fc8fb1": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { diff --git a/tests/translator/output/aws-cn/http_api_def_uri.json b/tests/translator/output/aws-cn/http_api_def_uri.json index 2bb57f180c..f285640fc4 100644 --- a/tests/translator/output/aws-cn/http_api_def_uri.json +++ b/tests/translator/output/aws-cn/http_api_def_uri.json @@ -48,7 +48,11 @@ "ApiId": { "Ref": "MyApi" }, - "AutoDeploy": true, + "AutoDeploy": true, + "DefaultRouteSettings": { + "ThrottlingBurstLimit": 50, + "ThrottlingRateLimit": 100.0 + }, "StageName": { "Fn::Join": [ "", @@ -57,13 +61,7 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { @@ -102,14 +100,11 @@ "ApiId": { "Ref": "MyApi2" }, - "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "AutoDeploy": true, + "DefaultRouteSettings": { + "ThrottlingRateLimit": 100 + }, + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +114,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,13 +144,7 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi.json b/tests/translator/output/aws-cn/http_api_existing_openapi.json index 1f4be7b61c..606deddcb9 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi.json @@ -1,4 +1,10 @@ { + "Parameters": { + "Timeout": { + "Default": 15000, + "Type": "Number" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -28,7 +34,10 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunction": { @@ -45,7 +54,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,15 +105,41 @@ } }, "paths": { + "/get/{something}/with/{params}": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] + } + }, "/basic": { "post": { "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": 10000 }, "security": [ { @@ -149,12 +184,15 @@ ], "isDefaultRoute": true, "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": { + "Ref": "Timeout" + } }, "responses": {} } @@ -180,6 +218,26 @@ } } }, + "x-amazon-apigateway-cors": { + "allowMethods": [ + "GET" + ], + "allowHeaders": [ + "x-apigateway-header" + ], + "allowOrigins": [ + "https://global.com", + "https://local.com" + ], + "maxAge": 6000 + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -210,10 +268,9 @@ } } } - }, - "openapi": "3.0.1" + } } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json index 07f4dd47aa..c674491ca4 100644 --- a/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-cn/http_api_existing_openapi_conditions.json @@ -37,7 +37,12 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM", + "Tag1": "value1", + "Tag2": "value2" + } } }, "HttpApiFunction": { @@ -104,7 +109,7 @@ "title": { "Ref": "AWS::StackName" } - }, + }, "paths": { "/basic": { "post": { @@ -114,7 +119,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -193,7 +198,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -268,7 +273,22 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/tests/translator/output/aws-cn/http_api_explicit_stage.json b/tests/translator/output/aws-cn/http_api_explicit_stage.json index 857ca45f78..eb33d241d4 100644 --- a/tests/translator/output/aws-cn/http_api_explicit_stage.json +++ b/tests/translator/output/aws-cn/http_api_explicit_stage.json @@ -1,4 +1,14 @@ { + "Parameters": { + "CorsParam": { + "Default": true, + "Type": "String" + }, + "PayloadFormatVersion": { + "Default": "1.0", + "Type": "String" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -35,7 +45,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -47,19 +57,34 @@ "MyApiProdStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, + "StageVariables": { + "VarName": "VarValue" + }, "ApiId": { "Ref": "MyApi" }, - "AutoDeploy": true, - "StageName": "Prod" + "StageName": "Prod", + "AccessLogSettings": { + "DestinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:LogGroupName", + "Format": "$context.requestId" + }, + "RouteSettings": { + "$default": { + "ThrottlingRateLimit": 0.7, + "DataTraceEnabled": true, + "ThrottlingBurstLimit": 300, + "LoggingLevel": "INFO" + } + }, + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -76,6 +101,9 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], "Tags": [ { "Value": "SAM", @@ -87,6 +115,7 @@ "MyApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { "version": "1.0", @@ -103,16 +132,29 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": { + "Ref": "PayloadFormatVersion" + } }, "isDefaultRoute": true, "responses": {} } } }, - "openapi": "3.0.1" + "x-amazon-apigateway-cors": { + "allowOrigins": [ + "*" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/http_api_with_cors.json b/tests/translator/output/aws-cn/http_api_with_cors.json new file mode 100644 index 0000000000..ccf90e087d --- /dev/null +++ b/tests/translator/output/aws-cn/http_api_with_cors.json @@ -0,0 +1,244 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n console.log(\"Hello from MyAuthFunction\")\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "HttpApiFunctionImplicitApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/implicit_http_api.json b/tests/translator/output/aws-cn/implicit_http_api.json index 6f589a57b5..c23badb552 100644 --- a/tests/translator/output/aws-cn/implicit_http_api.json +++ b/tests/translator/output/aws-cn/implicit_http_api.json @@ -7,16 +7,15 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -33,6 +32,10 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" + ], "Tags": [ { "Value": "SAM", @@ -44,10 +47,6 @@ "HttpApiFunction2Role": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -64,6 +63,77 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -91,22 +161,34 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} } }, - "/basic": { + "/get/{something}/with/{params}": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", "type": "aws_proxy", "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, - "responses": {} + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] } }, "$default": { @@ -117,28 +199,47 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} } + }, + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } }, - "HttpApiFunctionSimpleCasePermission": { + "HttpApiFunction2PathParametersPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "Principal": "apigateway.amazonaws.com", "FunctionName": { - "Ref": "HttpApiFunction" + "Ref": "HttpApiFunction2" }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/get/*/with/*", { "__Stage__": "*", "__ApiId__": { @@ -149,52 +250,6 @@ } } }, - "HttpApiFunction2": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunction2Role", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "HttpApiFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, "HttpApiFunction2Basic2Permission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -217,4 +272,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json index 34b58b18ac..b3e0950108 100644 --- a/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/aws-cn/implicit_http_api_auth_and_simple_case.json @@ -7,7 +7,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "RestApiFunctionRole": { @@ -55,7 +58,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,27 +99,7 @@ } }, "paths": { - "/scope3": { - "post": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" - }, - "payloadFormatVersion": "1.0" - }, - "security": [ - { - "OpenIdAuth": [ - "scope3" - ] - } - ], - "responses": {} - } - }, - "/someauth": { + "/defaultauth": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -124,13 +107,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -145,7 +127,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -165,14 +147,13 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -187,7 +168,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -197,7 +178,7 @@ "responses": {} } }, - "/defaultauth": { + "/scope3": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -205,13 +186,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope3" ] } ], @@ -219,6 +199,7 @@ } } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -233,26 +214,17 @@ "issuer": "https://www.example.com/v1/connect/oidc" } } - }, - "OpenIdAuth": { - "type": "openIdConnect", - "x-amazon-apigateway-authorizer": { - "identitySource": "$request.querystring.param", - "type": "jwt", - "jwtConfiguration": { - "audience": [ - "MyApi" - ], - "issuer": "https://www.example.com/v1/connect/oidc" - }, - "openIdConnectUrl": "https://www.example.com/v1/connect" - } } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json b/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json index 577ba4ed0d..90f1f1c0f9 100644 --- a/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/aws-cn/implicit_http_api_with_many_conditions.json @@ -222,7 +222,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -259,7 +259,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -296,7 +296,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -333,7 +333,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -370,7 +370,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ @@ -415,7 +415,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -452,7 +452,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -489,7 +489,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -526,7 +526,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -563,7 +563,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -600,7 +600,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -637,7 +637,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -653,6 +653,7 @@ ] } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2": { @@ -670,7 +671,12 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } }, "Condition": "ServerlessHttpApiCondition" @@ -1330,11 +1336,38 @@ "ServerlessHttpApiApiGatewayDefaultStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, "ApiId": { "Ref": "ServerlessHttpApi" }, - "AutoDeploy": true, - "StageName": "$default" + "RouteSettings": { + "GET /sub": { + "Fn::If": [ + "MyCondition", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "$default": { + "Fn::If": [ + "Cond", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } }, "Condition": "ServerlessHttpApiCondition" }, @@ -1470,4 +1503,4 @@ "Condition": "Cond3" } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/all_policy_templates.json b/tests/translator/output/aws-us-gov/all_policy_templates.json index 105c4a3732..9f9ac33a79 100644 --- a/tests/translator/output/aws-us-gov/all_policy_templates.json +++ b/tests/translator/output/aws-us-gov/all_policy_templates.json @@ -1485,6 +1485,91 @@ } ] } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy55", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutLifecycleConfiguration" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}", + { + "bucketName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:s3:::${bucketName}/*", + { + "bucketName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy56", + "PolicyDocument": { + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:BatchWriteItem" + ], + "Resource": [ + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}", + { + "tableName": "name" + } + ] + }, + { + "Fn::Sub": [ + "arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/index/*", + { + "tableName": "name" + } + ] + } + ] + } + ] + } + }, + { + "PolicyName": "KitchenSinkFunctionRolePolicy57", + "PolicyDocument": { + "Statement": [ + { + "Action": "events:PutEvents", + "Resource": { + "Fn::Sub": [ + "arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/${eventBusName}", + { + "eventBusName": "name" + } + ] + }, + "Effect": "Allow" + } + ] + } } ], "Tags": [ diff --git a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_http.json b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_http.json new file mode 100644 index 0000000000..cb2577debd --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_http.json @@ -0,0 +1,220 @@ +{ + "Parameters": { + "MyDomainName": { + "Default": "sam-example.com", + "Type": "String" + }, + "MyDomainCert": { + "Default": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "Type": "String" + } + }, + "Resources": { + "MyApibasicApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "basic", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV22dbf35af54": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "sam-example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "arn:aws:acm:us-east-1:123455353535:certificate/6c911401-620d-4d41-b89e-366c238bb2f3", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "HttpApiFunction": { + "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": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApibeginhereApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV22dbf35af54" + }, + "ApiMappingKey": "begin-here", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "RecordSetGroup1f4f569a7e": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "sam-example.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV22dbf35af54", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "sam-example.com" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + }, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics_http.json b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics_http.json new file mode 100644 index 0000000000..b3a6b181cf --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_basic_custom_domain_intrinsics_http.json @@ -0,0 +1,318 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Parameters": { + "MyDomainCert": { + "Default": "another-api-arn", + "Type": "String" + }, + "EndpointConf": { + "Default": "REGIONAL", + "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" + } + ] + }, + "Condition": "C1" + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/implicit": { + "Fn::If": [ + "C1", + { + "post": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyFunctionImplicitGetPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/implicit", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "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-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + }, + "Condition": "C1" + }, + "ApiGatewayDomainNameV29c93aac102": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": { + "Fn::Sub": "example-us-gov-west-1.com" + }, + "DomainNameConfigurations": [ + { + "CertificateArn": "another-api-arn", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyFunctionApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/get", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + }, + "Condition": "C1" + }, + "MyApigetApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV29c93aac102" + }, + "ApiMappingKey": "get", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + }, + "Condition": "C1" + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/get": { + "Fn::If": [ + "C1", + { + "put": { + "Fn::If": [ + "C1", + { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "C1", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + }, + "Condition": "C1" + }, + "MyApifetchApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV29c93aac102" + }, + "ApiMappingKey": "fetch", + "Stage": { + "Ref": "MyApiProdStage" + } + }, + "Condition": "C1" + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_hosted_zone_name_http.json b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_hosted_zone_name_http.json new file mode 100644 index 0000000000..f952f03ad8 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_hosted_zone_name_http.json @@ -0,0 +1,191 @@ +{ + "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" + } + ] + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "RecordSetGroup456ebaf280": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneName": "www.my-domain.com.", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + } + ] + } + }, + "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-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_http.json b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_http.json new file mode 100644 index 0000000000..c92bc22424 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_custom_domain_route53_http.json @@ -0,0 +1,209 @@ +{ + "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" + } + ] + } + }, + "MyApioneApiMapping": { + "Type": "AWS::ApiGatewayV2::ApiMapping", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "DomainName": { + "Ref": "ApiGatewayDomainNameV20caaf24ab1" + }, + "ApiMappingKey": "one", + "Stage": { + "Ref": "MyApiProdStage" + } + } + }, + "RecordSetGroupbd00d962a4": { + "Type": "AWS::Route53::RecordSetGroup", + "Properties": { + "HostedZoneId": "ZQ1UAL4EFZVME", + "RecordSets": [ + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "A", + "Name": "example.com" + }, + { + "AliasTarget": { + "HostedZoneId": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalHostedZoneId" + ] + }, + "DNSName": { + "Fn::GetAtt": [ + "ApiGatewayDomainNameV20caaf24ab1", + "RegionalDomainName" + ] + } + }, + "Type": "AAAA", + "Name": "example.com" + } + ] + } + }, + "ApiGatewayDomainNameV20caaf24ab1": { + "Type": "AWS::ApiGatewayV2::DomainName", + "Properties": { + "DomainName": "example.com", + "DomainNameConfigurations": [ + { + "CertificateArn": "cert-arn-in-us-east-1", + "EndpointType": "REGIONAL" + } + ], + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "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-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyFunctionFetchPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/fetch", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "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::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } + } + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_usageplans.json b/tests/translator/output/aws-us-gov/api_with_usageplans.json index 8309958bab..bda4c204ae 100644 --- a/tests/translator/output/aws-us-gov/api_with_usageplans.json +++ b/tests/translator/output/aws-us-gov/api_with_usageplans.json @@ -17,6 +17,24 @@ "Value": { "Fn::Sub": "https://${MyApiOne}.execute-api.${AWS::Region}.amazonaws.com/Prod/" } + }, + "ApiKey": { + "Description": "Api Key", + "Value": { + "Ref": "ServerlessApiKey" + } + }, + "UsagePlanKey": { + "Description": "Usage Plan Key", + "Value": { + "Ref": "MyApiTwoUsagePlanKey" + } + }, + "UsagePlan": { + "Description": "Usage Plan physical Id", + "Value": { + "Ref": "MyApiTwoUsagePlan" + } } }, "Resources": { diff --git a/tests/translator/output/aws-us-gov/explicit_http_api.json b/tests/translator/output/aws-us-gov/explicit_http_api.json index 1da7c4eee3..e3ff6cce64 100644 --- a/tests/translator/output/aws-us-gov/explicit_http_api.json +++ b/tests/translator/output/aws-us-gov/explicit_http_api.json @@ -7,7 +7,10 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "MyApi2ApiGatewayDefaultStage": { @@ -17,7 +20,10 @@ "Ref": "MyApi2" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunction": { @@ -69,23 +75,37 @@ "Properties": { "Body": { "info": { - "version": "1.0", "title": { - "Ref": "AWS::StackName" + "Fn::Sub": "${AWS::StackName}-Apiv2" } }, "paths": { "$default": { "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "isDefaultRoute": true, + "security": [ + { + "OAuth2": [] + } + ] + } + }, + "/": { + "get": { "x-amazon-apigateway-integration": { "httpMethod": "POST", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, - "isDefaultRoute": true, "security": [ { "OAuth2": [] @@ -95,6 +115,7 @@ } } }, + "openapi": "3.0", "components": { "securitySchemes": { "OAuth2": { @@ -112,7 +133,12 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } }, @@ -146,7 +172,7 @@ ] } }, - "HttpApiFunctionSimpleCase2Permission": { + "HttpApiFunctionBasePathPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", @@ -156,7 +182,7 @@ }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", { "__Stage__": "*", "__ApiId__": { @@ -170,6 +196,7 @@ "MyApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { "version": "1.0", @@ -186,7 +213,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ @@ -200,6 +227,7 @@ } } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "OAuth2": { @@ -217,9 +245,14 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json b/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json index 7cce5345a2..df8e5fcfb1 100644 --- a/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json +++ b/tests/translator/output/aws-us-gov/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -108,7 +120,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} @@ -126,7 +138,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "ApiApiGatewayDefaultStage": { @@ -136,7 +151,10 @@ "Ref": "Api" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } } } diff --git a/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json b/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json index 91663ba8f9..2849abc9a1 100644 --- a/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json +++ b/tests/translator/output/aws-us-gov/function_with_event_dest_basic.json @@ -121,21 +121,12 @@ "Runtime": "nodejs12.x" } }, - "MyTestFunctionVersion948f7f815d": { - "DeletionPolicy": "Retain", - "Type": "AWS::Lambda::Version", - "Properties": { - "FunctionName": { - "Ref": "MyTestFunction" - } - } - }, "MyTestFunctionAliaslive": { "Type": "AWS::Lambda::Alias", "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MyTestFunctionVersion948f7f815d", + "MyTestFunctionVersiondaf9da458d", "Version" ] }, @@ -144,6 +135,15 @@ }, "Name": "live" } + }, + "MyTestFunctionVersiondaf9da458d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } } } } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json index fcf9715da1..54ace4524a 100644 --- a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json @@ -169,8 +169,7 @@ "MySqsQueue", "Arn" ] - }, - "Type": "SQS" + } } }, "EventSourceArn": { @@ -227,8 +226,7 @@ "OnFailure": { "Destination": { "Ref": "MySnsTopic" - }, - "Type": "SNS" + } } } } diff --git a/tests/translator/output/aws-us-gov/globals_for_function.json b/tests/translator/output/aws-us-gov/globals_for_function.json index e436c5079c..31fce858f0 100644 --- a/tests/translator/output/aws-us-gov/globals_for_function.json +++ b/tests/translator/output/aws-us-gov/globals_for_function.json @@ -144,7 +144,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "FunctionWithOverridesVersion640128d35d", + "FunctionWithOverridesVersion096ed3b52b", "Version" ] }, @@ -211,7 +211,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MinimalFunctionVersionfb7aeaa544", + "MinimalFunctionVersion0a06fc8fb1", "Version" ] }, @@ -221,7 +221,7 @@ "Name": "live" } }, - "FunctionWithOverridesVersion640128d35d": { + "FunctionWithOverridesVersion096ed3b52b": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { @@ -230,7 +230,7 @@ } } }, - "MinimalFunctionVersionfb7aeaa544": { + "MinimalFunctionVersion0a06fc8fb1": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { diff --git a/tests/translator/output/aws-us-gov/http_api_def_uri.json b/tests/translator/output/aws-us-gov/http_api_def_uri.json index c800370291..5190687504 100644 --- a/tests/translator/output/aws-us-gov/http_api_def_uri.json +++ b/tests/translator/output/aws-us-gov/http_api_def_uri.json @@ -48,7 +48,11 @@ "ApiId": { "Ref": "MyApi" }, - "AutoDeploy": true, + "AutoDeploy": true, + "DefaultRouteSettings": { + "ThrottlingBurstLimit": 50, + "ThrottlingRateLimit": 100.0 + }, "StageName": { "Fn::Join": [ "", @@ -57,13 +61,7 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { @@ -102,14 +100,11 @@ "ApiId": { "Ref": "MyApi2" }, - "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "AutoDeploy": true, + "DefaultRouteSettings": { + "ThrottlingRateLimit": 100 + }, + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +114,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,13 +144,7 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi.json index 3750d237cc..5efdccc329 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi.json @@ -1,4 +1,10 @@ { + "Parameters": { + "Timeout": { + "Default": 15000, + "Type": "Number" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -28,7 +34,10 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunction": { @@ -45,7 +54,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,15 +105,41 @@ } }, "paths": { + "/get/{something}/with/{params}": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] + } + }, "/basic": { "post": { "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": 10000 }, "security": [ { @@ -149,12 +184,15 @@ ], "isDefaultRoute": true, "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": { + "Ref": "Timeout" + } }, "responses": {} } @@ -180,6 +218,26 @@ } } }, + "x-amazon-apigateway-cors": { + "allowMethods": [ + "GET" + ], + "allowHeaders": [ + "x-apigateway-header" + ], + "allowOrigins": [ + "https://global.com", + "https://local.com" + ], + "maxAge": 6000 + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -210,10 +268,9 @@ } } } - }, - "openapi": "3.0.1" + } } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json index 77517a7962..22e36ce32e 100644 --- a/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/aws-us-gov/http_api_existing_openapi_conditions.json @@ -37,7 +37,12 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM", + "Tag1": "value1", + "Tag2": "value2" + } } }, "HttpApiFunction": { @@ -114,7 +119,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -193,7 +198,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -268,7 +273,22 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/tests/translator/output/aws-us-gov/http_api_explicit_stage.json b/tests/translator/output/aws-us-gov/http_api_explicit_stage.json index 9c4fa4d5ac..6fbeb6689d 100644 --- a/tests/translator/output/aws-us-gov/http_api_explicit_stage.json +++ b/tests/translator/output/aws-us-gov/http_api_explicit_stage.json @@ -1,4 +1,14 @@ { + "Parameters": { + "CorsParam": { + "Default": true, + "Type": "String" + }, + "PayloadFormatVersion": { + "Default": "1.0", + "Type": "String" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -35,7 +45,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -47,19 +57,34 @@ "MyApiProdStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "StageVariables": { + "VarName": "VarValue" + }, + "AccessLogSettings": { + "DestinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:LogGroupName", + "Format": "$context.requestId" + }, + "StageName": "Prod", + "Tags": { + "httpapi:createdBy": "SAM" + }, "ApiId": { "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "Prod" + "RouteSettings": { + "$default": { + "ThrottlingRateLimit": 0.7, + "DataTraceEnabled": true, + "ThrottlingBurstLimit": 300, + "LoggingLevel": "INFO" + } + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -76,6 +101,9 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], "Tags": [ { "Value": "SAM", @@ -87,6 +115,7 @@ "MyApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { "version": "1.0", @@ -103,16 +132,29 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": { + "Ref": "PayloadFormatVersion" + } }, "isDefaultRoute": true, "responses": {} } } }, - "openapi": "3.0.1" + "x-amazon-apigateway-cors": { + "allowOrigins": [ + "*" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/http_api_with_cors.json b/tests/translator/output/aws-us-gov/http_api_with_cors.json new file mode 100644 index 0000000000..7b9ed1d105 --- /dev/null +++ b/tests/translator/output/aws-us-gov/http_api_with_cors.json @@ -0,0 +1,244 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n console.log(\"Hello from MyAuthFunction\")\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "HttpApiFunctionImplicitApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/implicit_http_api.json b/tests/translator/output/aws-us-gov/implicit_http_api.json index 7cf122de5e..713e890c64 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api.json @@ -7,16 +7,15 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -33,6 +32,10 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" + ], "Tags": [ { "Value": "SAM", @@ -44,10 +47,6 @@ "HttpApiFunction2Role": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -64,6 +63,77 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -91,22 +161,34 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} } }, - "/basic": { + "/get/{something}/with/{params}": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", "type": "aws_proxy", "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, - "responses": {} + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] } }, "$default": { @@ -117,28 +199,47 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} } + }, + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } }, - "HttpApiFunctionSimpleCasePermission": { + "HttpApiFunction2PathParametersPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "Principal": "apigateway.amazonaws.com", "FunctionName": { - "Ref": "HttpApiFunction" + "Ref": "HttpApiFunction2" }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/get/*/with/*", { "__Stage__": "*", "__ApiId__": { @@ -149,52 +250,6 @@ } } }, - "HttpApiFunction2": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunction2Role", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "HttpApiFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, "HttpApiFunction2Basic2Permission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -217,4 +272,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json index 3d5bce2b86..7ace3a5224 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api_auth_and_simple_case.json @@ -7,7 +7,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "RestApiFunctionRole": { @@ -55,7 +58,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,27 +99,7 @@ } }, "paths": { - "/scope3": { - "post": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" - }, - "payloadFormatVersion": "1.0" - }, - "security": [ - { - "OpenIdAuth": [ - "scope3" - ] - } - ], - "responses": {} - } - }, - "/someauth": { + "/defaultauth": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -124,13 +107,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -145,7 +127,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -165,14 +147,13 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -187,7 +168,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -197,7 +178,7 @@ "responses": {} } }, - "/defaultauth": { + "/scope3": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -205,13 +186,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope3" ] } ], @@ -219,6 +199,7 @@ } } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -233,26 +214,17 @@ "issuer": "https://www.example.com/v1/connect/oidc" } } - }, - "OpenIdAuth": { - "type": "openIdConnect", - "x-amazon-apigateway-authorizer": { - "identitySource": "$request.querystring.param", - "type": "jwt", - "jwtConfiguration": { - "audience": [ - "MyApi" - ], - "issuer": "https://www.example.com/v1/connect/oidc" - }, - "openIdConnectUrl": "https://www.example.com/v1/connect" - } } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json b/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json index b97e6db6b0..9f11855958 100644 --- a/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/aws-us-gov/implicit_http_api_with_many_conditions.json @@ -222,7 +222,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -259,7 +259,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -296,7 +296,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -333,7 +333,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -370,7 +370,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ @@ -415,7 +415,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -452,7 +452,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -489,7 +489,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -526,7 +526,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -563,7 +563,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -600,7 +600,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -637,7 +637,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -653,6 +653,7 @@ ] } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2": { @@ -670,7 +671,12 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } }, "Condition": "ServerlessHttpApiCondition" @@ -1330,11 +1336,38 @@ "ServerlessHttpApiApiGatewayDefaultStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, "ApiId": { "Ref": "ServerlessHttpApi" }, - "AutoDeploy": true, - "StageName": "$default" + "RouteSettings": { + "GET /sub": { + "Fn::If": [ + "MyCondition", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "$default": { + "Fn::If": [ + "Cond", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } }, "Condition": "ServerlessHttpApiCondition" }, @@ -1470,4 +1503,4 @@ "Condition": "Cond3" } } -} +} \ No newline at end of file diff --git a/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json index bfcd81c34b..62fd4320f5 100644 --- a/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json +++ b/tests/translator/output/error_api_gateway_responses_unknown_responseparameter_property.json @@ -1 +1 @@ -{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Invalid property 'SubStatusCode' in 'GatewayResponses' property 'UNAUTHORIZED'"} \ No newline at end of file +{"errorMessage":"Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Invalid property 'SubStatusCode' in 'GatewayResponses' property 'UNAUTHORIZED'."} diff --git a/tests/translator/output/error_api_invalid_auth.json b/tests/translator/output/error_api_invalid_auth.json index 1ed9fb0279..6cdd59dbed 100644 --- a/tests/translator/output/error_api_invalid_auth.json +++ b/tests/translator/output/error_api_invalid_auth.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 15. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizerNotDict] is invalid. Authorizer MyCognitoAuthorizer must be a dictionary. Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [NoIdentityOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. Resource with id [NoIdentitySourceOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context." -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 15. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property. Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger definition. Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers'. Resource with id [AuthorizerNotDict] is invalid. Authorizer MyCognitoAuthorizer must be a dictionary. Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary. Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID. Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [NoIdentityOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. Resource with id [NoIdentitySourceOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context." +} diff --git a/tests/translator/output/error_api_invalid_definitionuri.json b/tests/translator/output/error_api_invalid_definitionuri.json index d7760185e0..22a6e777eb 100644 --- a/tests/translator/output/error_api_invalid_definitionuri.json +++ b/tests/translator/output/error_api_invalid_definitionuri.json @@ -1,11 +1,11 @@ { "errors": [ { - "errorMessage": "Resource with id [Api] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter." + "errorMessage": "Resource with id [Api] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter." }, { - "errorMessage": "Resource with id [ApiWithBodyAndDefinitionUri] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" + "errorMessage": "Resource with id [ApiWithBodyAndDefinitionUri] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [Api] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter. Resource with id [ApiWithBodyAndDefinitionUri] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [Api] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter. Resource with id [ApiWithBodyAndDefinitionUri] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." +} diff --git a/tests/translator/output/error_api_invalid_request_model.json b/tests/translator/output/error_api_invalid_request_model.json index 337edf835e..1fc82b350b 100644 --- a/tests/translator/output/error_api_invalid_request_model.json +++ b/tests/translator/output/error_api_invalid_request_model.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [MissingModelFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set RequestModel [UnspecifiedModel] on API method [get] for path [/] because it wasn't defined in the API's Models. Resource with id [ModelsNotDictApi] is invalid. Invalid value for 'Models' property Resource with id [ModelsWithDefinitionUrlApi] is invalid. Models works only with inline Swagger specified in 'DefinitionBody' property Resource with id [ModelsWithInvalidDefinitionBodyApi] is invalid. Unable to add Models definitions because 'DefinitionBody' does not contain a valid Swagger Resource with id [NoModelFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set RequestModel [User] on API method [get] for path [/] because the related API does not define any Models." -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [MissingModelFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set RequestModel [UnspecifiedModel] on API method [get] for path [/] because it wasn't defined in the API's Models. Resource with id [ModelsNotDictApi] is invalid. Invalid value for 'Models' property Resource with id [ModelsWithDefinitionUrlApi] is invalid. Models works only with inline Swagger specified in 'DefinitionBody' property. Resource with id [ModelsWithInvalidDefinitionBodyApi] is invalid. Unable to add Models definitions because 'DefinitionBody' does not contain a valid Swagger definition. Resource with id [NoModelFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set RequestModel [User] on API method [get] for path [/] because the related API does not define any Models." +} diff --git a/tests/translator/output/error_api_invalid_restapiid.json b/tests/translator/output/error_api_invalid_restapiid.json index 58e049a957..aa4c86aa1d 100644 --- a/tests/translator/output/error_api_invalid_restapiid.json +++ b/tests/translator/output/error_api_invalid_restapiid.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [FunctionWithNonExistentApiReference] is invalid. Event with id [GetHtml] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template" + "errorMessage": "Resource with id [FunctionWithNonExistentApiReference] is invalid. Event with id [GetHtml] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionWithNonExistentApiReference] is invalid. Event with id [GetHtml] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionWithNonExistentApiReference] is invalid. Event with id [GetHtml] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template." } \ No newline at end of file diff --git a/tests/translator/output/error_api_with_custom_domains_invalid.json b/tests/translator/output/error_api_with_custom_domains_invalid.json index 1f60e2664e..d3b1fccfa9 100644 --- a/tests/translator/output/error_api_with_custom_domains_invalid.json +++ b/tests/translator/output/error_api_with_custom_domains_invalid.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [MyApi] is invalid. EndpointConfiguration for Custom Domains must be one of ['EDGE', 'REGIONAL'] Resource with id [ServerlessRestApi] is invalid. Custom Domains only works if both DomainName and CertificateArn are provided" + "errorMessage": "Resource with id [MyApi] is invalid. EndpointConfiguration for Custom Domains must be one of ['EDGE', 'REGIONAL']. Resource with id [ServerlessRestApi] is invalid. Custom Domains only works if both DomainName and CertificateArn are provided." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [MyApi] is invalid. EndpointConfiguration for Custom Domains must be one of ['EDGE', 'REGIONAL'] Resource with id [ServerlessRestApi] is invalid. Custom Domains only works if both DomainName and CertificateArn are provided" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [MyApi] is invalid. EndpointConfiguration for Custom Domains must be one of ['EDGE', 'REGIONAL']. Resource with id [ServerlessRestApi] is invalid. Custom Domains only works if both DomainName and CertificateArn are provided." } diff --git a/tests/translator/output/error_api_with_invalid_open_api_version.json b/tests/translator/output/error_api_with_invalid_open_api_version.json index df75e769d7..e5b5c73a5a 100644 --- a/tests/translator/output/error_api_with_invalid_open_api_version.json +++ b/tests/translator/output/error_api_with_invalid_open_api_version.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [MyApi] is invalid. The OpenApiVersion value must be of the format \"3.0.0\"" + "errorMessage": "Resource with id [MyApi] is invalid. The OpenApiVersion value must be of the format '3.0.0'." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. The OpenApiVersion value must be of the format \"3.0.0\"" - } \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. The OpenApiVersion value must be of the format '3.0.0'." + } diff --git a/tests/translator/output/error_cors_on_external_swagger.json b/tests/translator/output/error_cors_on_external_swagger.json index ea702cd524..528129a2b1 100644 --- a/tests/translator/output/error_cors_on_external_swagger.json +++ b/tests/translator/output/error_cors_on_external_swagger.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [ExplicitApi] is invalid. Cors works only with inline Swagger specified in 'DefinitionBody' property" + "errorMessage": "Resource with id [ExplicitApi] is invalid. Cors works only with inline Swagger specified in 'DefinitionBody' property." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Cors works only with inline Swagger specified in 'DefinitionBody' property" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. Cors works only with inline Swagger specified in 'DefinitionBody' property." } diff --git a/tests/translator/output/error_function_invalid_codeuri.json b/tests/translator/output/error_function_invalid_codeuri.json index 1307352b44..ce3df94813 100644 --- a/tests/translator/output/error_function_invalid_codeuri.json +++ b/tests/translator/output/error_function_invalid_codeuri.json @@ -4,5 +4,5 @@ "errorMessage": "Resource with id [Function] is invalid. 'CodeUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. 'CodeUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter." -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. 'CodeUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter." +} diff --git a/tests/translator/output/error_function_with_deployment_preference_missing_alias.json b/tests/translator/output/error_function_with_deployment_preference_missing_alias.json index fb505d7586..0c9f4ede35 100644 --- a/tests/translator/output/error_function_with_deployment_preference_missing_alias.json +++ b/tests/translator/output/error_function_with_deployment_preference_missing_alias.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [MinimalFunction] is invalid. 'DeploymentPreference' requires AutoPublishAlias property to be specified" + "errorMessage": "Resource with id [MinimalFunction] is invalid. 'DeploymentPreference' requires AutoPublishAlias property to be specified." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MinimalFunction] is invalid. 'DeploymentPreference' requires AutoPublishAlias property to be specified" -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MinimalFunction] is invalid. 'DeploymentPreference' requires AutoPublishAlias property to be specified." +} diff --git a/tests/translator/output/error_http_api_def_body_uri.json b/tests/translator/output/error_http_api_def_body_uri.json index 4d7ef9f0e6..dd455feb78 100644 --- a/tests/translator/output/error_http_api_def_body_uri.json +++ b/tests/translator/output/error_http_api_def_body_uri.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [MyApi] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" + "errorMessage": "Resource with id [MyApi] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." } \ No newline at end of file diff --git a/tests/translator/output/error_http_api_event_invalid_api.json b/tests/translator/output/error_http_api_event_invalid_api.json index d979bb8bf3..acbe70f0ee 100644 --- a/tests/translator/output/error_http_api_event_invalid_api.json +++ b/tests/translator/output/error_http_api_event_invalid_api.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template" + "errorMessage": "Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. ApiId must be a valid reference to an 'AWS::Serverless::HttpApi' resource in same template." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. RestApiId must be a valid reference to an 'AWS::Serverless::Api' resource in same template" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HttpApiFunction] is invalid. Event with id [Api] is invalid. ApiId must be a valid reference to an 'AWS::Serverless::HttpApi' resource in same template." } \ No newline at end of file diff --git a/tests/translator/output/error_http_api_invalid_auth.json b/tests/translator/output/error_http_api_invalid_auth.json index ade985d7c1..e5bdcb426d 100644 --- a/tests/translator/output/error_http_api_invalid_auth.json +++ b/tests/translator/output/error_http_api_invalid_auth.json @@ -4,5 +4,5 @@ "errorMessage": "Resource with id [Function] is invalid. Event with id [Api] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because the related API does not define any Authorizers. Resource with id [Function2] is invalid. Event with id [Api2] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because it wasn't defined in the API's Authorizers. Resource with id [Function3] is invalid. Event with id [Api3] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [Function4] is invalid. Event with id [Api4] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'AuthorizationScopes' must be a list of strings." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 4. Resource with id [Function] is invalid. Event with id [Api] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because the related API does not define any Authorizers. Resource with id [Function2] is invalid. Event with id [Api2] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because it wasn't defined in the API's Authorizers. Resource with id [Function3] is invalid. Event with id [Api3] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [Function4] is invalid. Event with id [Api4] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'AuthorizationScopes' must be a list of strings." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [Function] is invalid. Event with id [Api] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because the related API does not define any Authorizers. Resource with id [Function2] is invalid. Event with id [Api2] is invalid. Unable to set Authorizer [myAuth] on API method [x-amazon-apigateway-any-method] for path [$default] because it wasn't defined in the API's Authorizers. Resource with id [Function3] is invalid. Event with id [Api3] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [Function4] is invalid. Event with id [Api4] is invalid. Unable to set Authorizer on API method [x-amazon-apigateway-any-method] for path [$default] because 'AuthorizationScopes' must be a list of strings. Resource with id [MyApi5] is invalid. 'OpenIdConnectUrl' is no longer a supported property for authorizer 'OIDC'. Please refer to the AWS SAM documentation." } \ No newline at end of file diff --git a/tests/translator/output/error_http_api_tags.json b/tests/translator/output/error_http_api_tags.json new file mode 100644 index 0000000000..97bd009e4e --- /dev/null +++ b/tests/translator/output/error_http_api_tags.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Api] is invalid. Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Api] is invalid. Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition." +} \ No newline at end of file diff --git a/tests/translator/output/error_http_api_tags_def_uri.json b/tests/translator/output/error_http_api_tags_def_uri.json new file mode 100644 index 0000000000..5b9eaa569a --- /dev/null +++ b/tests/translator/output/error_http_api_tags_def_uri.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Api] is invalid. Tags works only with inline OpenApi specified in the 'DefinitionBody' property." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Api] is invalid. Tags works only with inline OpenApi specified in the 'DefinitionBody' property." +} \ No newline at end of file diff --git a/tests/translator/output/error_http_api_with_cors_def_uri.json b/tests/translator/output/error_http_api_with_cors_def_uri.json new file mode 100644 index 0000000000..776b9bddfd --- /dev/null +++ b/tests/translator/output/error_http_api_with_cors_def_uri.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [MyApi] is invalid. Cors works only with inline OpenApi specified in 'DefinitionBody' property." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MyApi] is invalid. Cors works only with inline OpenApi specified in 'DefinitionBody' property." +} \ No newline at end of file diff --git a/tests/translator/output/error_multiple_resource_errors.json b/tests/translator/output/error_multiple_resource_errors.json index bdc58656f8..ec68793ae4 100644 --- a/tests/translator/output/error_multiple_resource_errors.json +++ b/tests/translator/output/error_multiple_resource_errors.json @@ -1,7 +1,7 @@ { "errors": [ { - "errorMessage": "Resource with id [BadCodeUriTypeFunction] is invalid. 'CodeUri' requires Bucket and Key properties to be specified" + "errorMessage": "Resource with id [BadCodeUriTypeFunction] is invalid. 'CodeUri' requires Bucket and Key properties to be specified." }, { "errorMessage": "Resource with id [ExternalS3Function] is invalid. Event with id [S3Event] is invalid. S3 events must reference an S3 bucket in the same template." @@ -13,8 +13,8 @@ "errorMessage": "Resource with id [BadDefinitionUriApi] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter." }, { - "errorMessage": "Resource with id [BadDefinitionUriTypeApi] is invalid. 'DefinitionUri' requires Bucket and Key properties to be specified" + "errorMessage": "Resource with id [BadDefinitionUriTypeApi] is invalid. 'DefinitionUri' requires Bucket and Key properties to be specified." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [BadCodeUriFunction] is invalid. 'CodeUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter. Resource with id [BadCodeUriTypeFunction] is invalid. 'CodeUri' requires Bucket and Key properties to be specified Resource with id [BadDefinitionUriApi] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form \"s3://bucket/key\" with optional versionId query parameter. Resource with id [BadDefinitionUriTypeApi] is invalid. 'DefinitionUri' requires Bucket and Key properties to be specified Resource with id [ExternalS3Function] is invalid. Event with id [S3Event] is invalid. S3 events must reference an S3 bucket in the same template." -} \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 5. Resource with id [BadCodeUriFunction] is invalid. 'CodeUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter. Resource with id [BadCodeUriTypeFunction] is invalid. 'CodeUri' requires Bucket and Key properties to be specified. Resource with id [BadDefinitionUriApi] is invalid. 'DefinitionUri' is not a valid S3 Uri of the form 's3://bucket/key' with optional versionId query parameter. Resource with id [BadDefinitionUriTypeApi] is invalid. 'DefinitionUri' requires Bucket and Key properties to be specified. Resource with id [ExternalS3Function] is invalid. Event with id [S3Event] is invalid. S3 events must reference an S3 bucket in the same template." +} diff --git a/tests/translator/output/explicit_http_api.json b/tests/translator/output/explicit_http_api.json index 920023cbe2..8a8a6dab6a 100644 --- a/tests/translator/output/explicit_http_api.json +++ b/tests/translator/output/explicit_http_api.json @@ -1,61 +1,67 @@ { "Resources": { "MyApiApiGatewayDefaultStage": { - "Type": "AWS::ApiGatewayV2::Stage", + "Type": "AWS::ApiGatewayV2::Stage", "Properties": { "ApiId": { "Ref": "MyApi" - }, - "AutoDeploy": true, - "StageName": "$default" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } - }, + }, "MyApi2ApiGatewayDefaultStage": { - "Type": "AWS::ApiGatewayV2::Stage", + "Type": "AWS::ApiGatewayV2::Stage", "Properties": { "ApiId": { "Ref": "MyApi2" - }, - "AutoDeploy": true, - "StageName": "$default" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } - }, + }, "HttpApiFunction": { - "Type": "AWS::Lambda::Function", + "Type": "AWS::Lambda::Function", "Properties": { - "Handler": "index.restapi", + "Handler": "index.restapi", "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "todo_list.zip" - }, + }, "Role": { "Fn::GetAtt": [ - "HttpApiFunctionRole", + "HttpApiFunctionRole", "Arn" ] - }, - "Runtime": "python3.7", + }, + "Runtime": "python3.7", "Tags": [ { - "Value": "SAM", + "Value": "SAM", "Key": "lambda:createdBy" } ] } - }, + }, "HttpApiFunctionSimpleCasePermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Ref": "HttpApiFunction" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", { - "__Stage__": "*", + "__Stage__": "*", "__ApiId__": { "Ref": "MyApi" } @@ -63,70 +69,90 @@ ] } } - }, + }, "MyApi2": { - "Type": "AWS::ApiGatewayV2::Api", + "Type": "AWS::ApiGatewayV2::Api", "Properties": { "Body": { "info": { - "version": "1.0", "title": { - "Ref": "AWS::StackName" + "Fn::Sub": "${AWS::StackName}-Apiv2" } - }, + }, "paths": { "$default": { "x-amazon-apigateway-any-method": { "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "isDefaultRoute": true, + "security": [ + { + "OAuth2": [] + } + ] + } + }, + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" - }, - "payloadFormatVersion": "1.0" - }, - "isDefaultRoute": true, + }, + "payloadFormatVersion": "2.0" + }, "security": [ { "OAuth2": [] } - ], + ], "responses": {} } } - }, + }, + "openapi": "3.0", "components": { "securitySchemes": { "OAuth2": { - "type": "oauth2", + "type": "oauth2", "x-amazon-apigateway-authorizer": { - "identitySource": "$request.querystring.param", - "type": "jwt", + "identitySource": "$request.querystring.param", + "type": "jwt", "jwtConfiguration": { "audience": [ "MyApi" - ], + ], "issuer": "https://www.example.com/v1/connect/oidc" } } } } - }, - "openapi": "3.0.1" + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } - }, + }, "HttpApiFunctionRole": { - "Type": "AWS::IAM::Role", + "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { - "Version": "2012-10-17", + "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" - ], - "Effect": "Allow", + ], + "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com" @@ -134,31 +160,31 @@ } } ] - }, + }, "ManagedPolicyArns": [ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], + ], "Tags": [ { - "Value": "SAM", + "Value": "SAM", "Key": "lambda:createdBy" } ] } - }, - "HttpApiFunctionSimpleCase2Permission": { - "Type": "AWS::Lambda::Permission", + }, + "HttpApiFunctionBasePathPermission": { + "Type": "AWS::Lambda::Permission", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Ref": "HttpApiFunction" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", { - "__Stage__": "*", + "__Stage__": "*", "__ApiId__": { "Ref": "MyApi2" } @@ -166,60 +192,67 @@ ] } } - }, + }, "MyApi": { - "Type": "AWS::ApiGatewayV2::Api", + "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { - "version": "1.0", + "version": "1.0", "title": { "Ref": "AWS::StackName" } - }, + }, "paths": { "$default": { "x-amazon-apigateway-any-method": { "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", + "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" - }, - "payloadFormatVersion": "1.0" - }, - "isDefaultRoute": true, + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, "security": [ { "OAuth2": [ "scope4" ] } - ], + ], "responses": {} } } - }, + }, + "openapi": "3.0.1", "components": { "securitySchemes": { "OAuth2": { - "type": "oauth2", + "type": "oauth2", "x-amazon-apigateway-authorizer": { - "identitySource": "$request.querystring.param", - "type": "jwt", + "identitySource": "$request.querystring.param", + "type": "jwt", "jwtConfiguration": { "audience": [ "MyApi" - ], + ], "issuer": "https://www.example.com/v1/connect/oidc" } } } } - }, - "openapi": "3.0.1" + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/explicit_http_api_minimum.json b/tests/translator/output/explicit_http_api_minimum.json index ae96554bc0..37be2617d8 100644 --- a/tests/translator/output/explicit_http_api_minimum.json +++ b/tests/translator/output/explicit_http_api_minimum.json @@ -54,6 +54,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": {}, "openapi": "3.0.1" } @@ -99,6 +105,12 @@ "Ref": "AWS::StackName" } }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], "paths": { "$default": { "x-amazon-apigateway-any-method": { @@ -108,7 +120,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} @@ -126,7 +138,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "ApiApiGatewayDefaultStage": { @@ -136,7 +151,10 @@ "Ref": "Api" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } } } diff --git a/tests/translator/output/function_with_event_dest_basic.json b/tests/translator/output/function_with_event_dest_basic.json index 5aae94593c..e845f151f5 100644 --- a/tests/translator/output/function_with_event_dest_basic.json +++ b/tests/translator/output/function_with_event_dest_basic.json @@ -121,21 +121,12 @@ "Runtime": "nodejs12.x" } }, - "MyTestFunctionVersion948f7f815d": { - "DeletionPolicy": "Retain", - "Type": "AWS::Lambda::Version", - "Properties": { - "FunctionName": { - "Ref": "MyTestFunction" - } - } - }, "MyTestFunctionAliaslive": { "Type": "AWS::Lambda::Alias", "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MyTestFunctionVersion948f7f815d", + "MyTestFunctionVersiondaf9da458d", "Version" ] }, @@ -144,6 +135,15 @@ }, "Name": "live" } + }, + "MyTestFunctionVersiondaf9da458d": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "MyTestFunction" + } + } } } } \ No newline at end of file diff --git a/tests/translator/output/function_with_event_source_mapping.json b/tests/translator/output/function_with_event_source_mapping.json index 7efa74c2ab..c9d00a9c4a 100644 --- a/tests/translator/output/function_with_event_source_mapping.json +++ b/tests/translator/output/function_with_event_source_mapping.json @@ -169,8 +169,7 @@ "MySqsQueue", "Arn" ] - }, - "Type": "SQS" + } } }, "EventSourceArn": { @@ -227,8 +226,7 @@ "OnFailure": { "Destination": { "Ref": "MySnsTopic" - }, - "Type": "SNS" + } } } } diff --git a/tests/translator/output/globals_for_function.json b/tests/translator/output/globals_for_function.json index 0ad54729b0..6174a06d0b 100644 --- a/tests/translator/output/globals_for_function.json +++ b/tests/translator/output/globals_for_function.json @@ -144,7 +144,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "FunctionWithOverridesVersion640128d35d", + "FunctionWithOverridesVersion096ed3b52b", "Version" ] }, @@ -211,7 +211,7 @@ "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MinimalFunctionVersionfb7aeaa544", + "MinimalFunctionVersion0a06fc8fb1", "Version" ] }, @@ -221,7 +221,7 @@ "Name": "live" } }, - "FunctionWithOverridesVersion640128d35d": { + "FunctionWithOverridesVersion096ed3b52b": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { @@ -230,7 +230,7 @@ } } }, - "MinimalFunctionVersionfb7aeaa544": { + "MinimalFunctionVersion0a06fc8fb1": { "DeletionPolicy": "Retain", "Type": "AWS::Lambda::Version", "Properties": { diff --git a/tests/translator/output/http_api_def_uri.json b/tests/translator/output/http_api_def_uri.json index ea5493bd81..fd5dedd146 100644 --- a/tests/translator/output/http_api_def_uri.json +++ b/tests/translator/output/http_api_def_uri.json @@ -45,10 +45,14 @@ "MyApiStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, "ApiId": { "Ref": "MyApi" }, - "AutoDeploy": true, + "DefaultRouteSettings": { + "ThrottlingBurstLimit": 50, + "ThrottlingRateLimit": 100.0 + }, "StageName": { "Fn::Join": [ "", @@ -57,21 +61,12 @@ "Name" ] ] - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -88,6 +83,9 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], "Tags": [ { "Value": "SAM", @@ -99,17 +97,14 @@ "MyApi2ApiGatewayDefaultStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, "ApiId": { "Ref": "MyApi2" }, - "AutoDeploy": true, - "StageName": "$default", - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + "DefaultRouteSettings": { + "ThrottlingRateLimit": 100.0 + }, + "StageName": "$default" } }, "MyApi2": { @@ -119,13 +114,7 @@ "Version": "version", "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } }, "FunctionApi2Permission": { @@ -155,14 +144,8 @@ "BodyS3Location": { "Bucket": "bucket", "Key": "key" - }, - "Tags": [ - { - "Value": "value", - "Key": "Tag" - } - ] + } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/http_api_existing_openapi.json b/tests/translator/output/http_api_existing_openapi.json index bf3ce13481..ea696cf89f 100644 --- a/tests/translator/output/http_api_existing_openapi.json +++ b/tests/translator/output/http_api_existing_openapi.json @@ -1,4 +1,10 @@ { + "Parameters": { + "Timeout": { + "Default": 15000, + "Type": "Number" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -28,7 +34,10 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunction": { @@ -45,7 +54,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,15 +105,41 @@ } }, "paths": { + "/get/{something}/with/{params}": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] + } + }, "/basic": { "post": { "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": 10000 }, "security": [ { @@ -149,12 +184,15 @@ ], "isDefaultRoute": true, "x-amazon-apigateway-integration": { - "httpMethod": "POST", + "payloadFormatVersion": "1.0", "type": "aws_proxy", "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "httpMethod": "POST", + "timeoutInMillis": { + "Ref": "Timeout" + } }, "responses": {} } @@ -180,6 +218,26 @@ } } }, + "x-amazon-apigateway-cors": { + "allowMethods": [ + "GET" + ], + "allowHeaders": [ + "x-apigateway-header" + ], + "allowOrigins": [ + "https://global.com", + "https://local.com" + ], + "maxAge": 6000 + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -210,10 +268,9 @@ } } } - }, - "openapi": "3.0.1" + } } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/http_api_existing_openapi_conditions.json b/tests/translator/output/http_api_existing_openapi_conditions.json index 1c5adc9c26..f1a6d177d5 100644 --- a/tests/translator/output/http_api_existing_openapi_conditions.json +++ b/tests/translator/output/http_api_existing_openapi_conditions.json @@ -37,7 +37,12 @@ "Ref": "MyApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM", + "Tag1": "value1", + "Tag2": "value2" + } } }, "HttpApiFunction": { @@ -104,7 +109,7 @@ "title": { "Ref": "AWS::StackName" } - }, + }, "paths": { "/basic": { "post": { @@ -114,7 +119,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${DifferentFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -193,7 +198,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -268,7 +273,22 @@ } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + }, + { + "name": "Tag1", + "x-amazon-apigateway-tag-value": "value1", + "description": "this tag exists, but doesn't have an amazon extension value" + }, + { + "name": "Tag2", + "x-amazon-apigateway-tag-value": "value2" + } + ] } } } diff --git a/tests/translator/output/http_api_explicit_stage.json b/tests/translator/output/http_api_explicit_stage.json index bc407fb3bc..6f827ff3a3 100644 --- a/tests/translator/output/http_api_explicit_stage.json +++ b/tests/translator/output/http_api_explicit_stage.json @@ -1,4 +1,14 @@ { + "Parameters": { + "CorsParam": { + "Default": true, + "Type": "String" + }, + "PayloadFormatVersion": { + "Default": "1.0", + "Type": "String" + } + }, "Resources": { "HttpApiFunctionSimpleCasePermission": { "Type": "AWS::Lambda::Permission", @@ -35,7 +45,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -47,19 +57,34 @@ "MyApiProdStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, + "StageVariables": { + "VarName": "VarValue" + }, "ApiId": { "Ref": "MyApi" }, - "AutoDeploy": true, - "StageName": "Prod" + "StageName": "Prod", + "AccessLogSettings": { + "DestinationArn": "arn:aws:logs:us-east-1:123456789012:log-group:LogGroupName", + "Format": "$context.requestId" + }, + "RouteSettings": { + "$default": { + "ThrottlingRateLimit": 0.7, + "DataTraceEnabled": true, + "ThrottlingBurstLimit": 300, + "LoggingLevel": "INFO" + } + }, + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -76,6 +101,9 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], "Tags": [ { "Value": "SAM", @@ -87,6 +115,7 @@ "MyApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { + "FailOnWarnings": true, "Body": { "info": { "version": "1.0", @@ -103,16 +132,29 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": { + "Ref": "PayloadFormatVersion" + } }, "isDefaultRoute": true, "responses": {} } } }, - "openapi": "3.0.1" + "x-amazon-apigateway-cors": { + "allowOrigins": [ + "*" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/http_api_with_cors.json b/tests/translator/output/http_api_with_cors.json new file mode 100644 index 0000000000..a24a91009a --- /dev/null +++ b/tests/translator/output/http_api_with_cors.json @@ -0,0 +1,244 @@ +{ + "Conditions": { + "C1": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "MyApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "MyApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApiApiGatewayDefaultStage": { + "Type": "AWS::ApiGatewayV2::Stage", + "Properties": { + "ApiId": { + "Ref": "ServerlessHttpApi" + }, + "AutoDeploy": true, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } + } + }, + "ServerlessHttpApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "ZipFile": "exports.handler = async (event) => {\n console.log(\"Hello from MyAuthFunction\")\n return {\n statusCode: 200,\n body: JSON.stringify(event),\n headers: {}\n }\n}\n" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "HttpApiFunctionImplicitApiPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunctionRole": { + "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" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGatewayV2::Api", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "x-amazon-apigateway-cors": { + "Fn::If": [ + "C1", + { + "allowHeaders": [ + "x-apigateway-header" + ], + "allowMethods": [ + "GET" + ], + "allowOrigins": [ + "https://foo.com" + ], + "exposeHeaders": [ + "x-amzn-header" + ] + }, + "AWS::NoValue" + ] + }, + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/implicit_http_api.json b/tests/translator/output/implicit_http_api.json index c2ba5006e3..146970cf60 100644 --- a/tests/translator/output/implicit_http_api.json +++ b/tests/translator/output/implicit_http_api.json @@ -7,16 +7,15 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "HttpApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -33,6 +32,10 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" + ], "Tags": [ { "Value": "SAM", @@ -44,10 +47,6 @@ "HttpApiFunction2Role": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -64,6 +63,77 @@ } ] }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HttpApiFunctionSimpleCasePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HttpApiFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "ServerlessHttpApi" + } + } + ] + } + } + }, + "HttpApiFunction2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunction2Role", + "Arn" + ] + }, + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -91,22 +161,34 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} } }, - "/basic": { + "/get/{something}/with/{params}": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", "type": "aws_proxy", "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction2.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, - "responses": {} + "responses": {}, + "parameters": [ + { + "required": true, + "name": "something", + "in": "path" + }, + { + "required": true, + "name": "params", + "in": "path" + } + ] } }, "$default": { @@ -117,28 +199,47 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "responses": {} } + }, + "/basic": { + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunction.Arn}/invocations" + }, + "payloadFormatVersion": "2.0" + }, + "responses": {} + } } }, - "openapi": "3.0.1" + "openapi": "3.0.1", + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } }, - "HttpApiFunctionSimpleCasePermission": { + "HttpApiFunction2PathParametersPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", "Principal": "apigateway.amazonaws.com", "FunctionName": { - "Ref": "HttpApiFunction" + "Ref": "HttpApiFunction2" }, "SourceArn": { "Fn::Sub": [ - "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*", + "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/get/*/with/*", { "__Stage__": "*", "__ApiId__": { @@ -149,52 +250,6 @@ } } }, - "HttpApiFunction2": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunction2Role", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, - "HttpApiFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Handler": "index.restapi", - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "todo_list.zip" - }, - "Role": { - "Fn::GetAtt": [ - "HttpApiFunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ] - } - }, "HttpApiFunction2Basic2Permission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -217,4 +272,4 @@ } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/implicit_http_api_auth_and_simple_case.json b/tests/translator/output/implicit_http_api_auth_and_simple_case.json index 6bbfafa11c..4f518881f6 100644 --- a/tests/translator/output/implicit_http_api_auth_and_simple_case.json +++ b/tests/translator/output/implicit_http_api_auth_and_simple_case.json @@ -7,7 +7,10 @@ "Ref": "ServerlessHttpApi" }, "AutoDeploy": true, - "StageName": "$default" + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } } }, "RestApiFunctionRole": { @@ -55,7 +58,7 @@ "Arn" ] }, - "Runtime": "nodejs12.x", + "Runtime": "nodejs12.x", "Tags": [ { "Value": "SAM", @@ -96,27 +99,7 @@ } }, "paths": { - "/scope3": { - "post": { - "x-amazon-apigateway-integration": { - "httpMethod": "POST", - "type": "aws_proxy", - "uri": { - "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" - }, - "payloadFormatVersion": "1.0" - }, - "security": [ - { - "OpenIdAuth": [ - "scope3" - ] - } - ], - "responses": {} - } - }, - "/someauth": { + "/defaultauth": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -124,13 +107,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -145,7 +127,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -165,14 +147,13 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope4" ] } ], @@ -187,7 +168,7 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { @@ -197,7 +178,7 @@ "responses": {} } }, - "/defaultauth": { + "/scope3": { "post": { "x-amazon-apigateway-integration": { "httpMethod": "POST", @@ -205,13 +186,12 @@ "uri": { "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiFunction.Arn}/invocations" }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "security": [ { - "OpenIdAuth": [ - "scope1", - "scope2" + "oauth2Auth": [ + "scope3" ] } ], @@ -219,6 +199,7 @@ } } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2Auth": { @@ -233,26 +214,17 @@ "issuer": "https://www.example.com/v1/connect/oidc" } } - }, - "OpenIdAuth": { - "type": "openIdConnect", - "x-amazon-apigateway-authorizer": { - "identitySource": "$request.querystring.param", - "type": "jwt", - "jwtConfiguration": { - "audience": [ - "MyApi" - ], - "issuer": "https://www.example.com/v1/connect/oidc" - }, - "openIdConnectUrl": "https://www.example.com/v1/connect" - } } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/implicit_http_api_with_many_conditions.json b/tests/translator/output/implicit_http_api_with_many_conditions.json index 86e20884ef..c4b9618b8c 100644 --- a/tests/translator/output/implicit_http_api_with_many_conditions.json +++ b/tests/translator/output/implicit_http_api_with_many_conditions.json @@ -222,7 +222,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -259,7 +259,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -296,7 +296,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -333,7 +333,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -370,7 +370,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "isDefaultRoute": true, "security": [ @@ -415,7 +415,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -452,7 +452,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -489,7 +489,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -526,7 +526,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -563,7 +563,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -600,7 +600,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -637,7 +637,7 @@ } ] }, - "payloadFormatVersion": "1.0" + "payloadFormatVersion": "2.0" }, "responses": {} }, @@ -653,6 +653,7 @@ ] } }, + "openapi": "3.0.1", "components": { "securitySchemes": { "oauth2": { @@ -670,7 +671,12 @@ } } }, - "openapi": "3.0.1" + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ] } }, "Condition": "ServerlessHttpApiCondition" @@ -1330,11 +1336,38 @@ "ServerlessHttpApiApiGatewayDefaultStage": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { + "AutoDeploy": true, "ApiId": { "Ref": "ServerlessHttpApi" }, - "AutoDeploy": true, - "StageName": "$default" + "RouteSettings": { + "GET /sub": { + "Fn::If": [ + "MyCondition", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + }, + "$default": { + "Fn::If": [ + "Cond", + { + "ThrottlingBurstLimit": 200 + }, + { + "Ref": "AWS::NoValue" + } + ] + } + }, + "StageName": "$default", + "Tags": { + "httpapi:createdBy": "SAM" + } }, "Condition": "ServerlessHttpApiCondition" }, @@ -1470,4 +1503,4 @@ "Condition": "Cond3" } } -} +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 7a271f5076..304d50f2cf 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -339,6 +339,10 @@ def test_transform_success(self, testcase, partition_with_region): "api_with_basic_custom_domain_intrinsics", "api_with_custom_domain_route53", "api_with_custom_domain_route53_hosted_zone_name", + "api_with_basic_custom_domain_http", + "api_with_basic_custom_domain_intrinsics_http", + "api_with_custom_domain_route53_http", + "api_with_custom_domain_route53_hosted_zone_name_http", "implicit_http_api", "explicit_http_api_minimum", "implicit_http_api_auth_and_simple_case", @@ -348,6 +352,7 @@ def test_transform_success(self, testcase, partition_with_region): "http_api_explicit_stage", "http_api_def_uri", "explicit_http_api", + "http_api_with_cors", ], [ ("aws", "ap-southeast-1"), @@ -602,6 +607,8 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_http_api_event_invalid_api", "error_http_api_invalid_auth", "error_http_api_invalid_openapi", + "error_http_api_tags", + "error_http_api_tags_def_uri", "error_implicit_http_api_method", "error_implicit_http_api_path", "error_http_api_event_multiple_same_path", @@ -609,6 +616,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_function_with_event_dest_type", "error_function_with_api_key_false", "error_api_with_usage_plan_invalid_parameter", + "error_http_api_with_cors_def_uri", ], ) @patch("boto3.session.Session.region_name", "ap-southeast-1") diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 4aaaa7d1a2..241ed54638 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -16,6 +16,7 @@ The AWS Serverless Application Model (SAM) is licensed under [The Apache License * [Event source types](#event-source-types) * [Property types](#property-types) * [Data types](#data-types) + * [Referable properties of SAM resources](#referable-properties-of-sam-resources) ## Introduction @@ -217,7 +218,7 @@ Layers: Creates a collection of Amazon API Gateway resources and methods that can be invoked through HTTPS endpoints. -An `AWS::Serverless::Api` resource need not be explicitly added to a AWS Serverless Application Definition template. A resource of this type is implicitly created from the union of [Api](#api) events defined on `AWS::Serverless::Function` resources defined in the template that do not refer to an `AWS::Serverless::Api` resource. An `AWS::Serverless::Api` resource should be used to define and document the API using [OpenAPI](https://github.com/OAI/OpenAPI-Specification), which provides more ability to configure the underlying Amazon API Gateway resources. +An `AWS::Serverless::Api` resource need not be explicitly added to a AWS Serverless Application Model template. A resource of this type is implicitly created from the union of [Api](#api) events defined on `AWS::Serverless::Function` resources defined in the template that do not refer to an `AWS::Serverless::Api` resource. An `AWS::Serverless::Api` resource should be used to define and document the API using [OpenAPI](https://github.com/OAI/OpenAPI-Specification), which provides more ability to configure the underlying Amazon API Gateway resources. ##### Properties @@ -266,7 +267,7 @@ SAM will generate an API Gateway Stage and API Gateway Deployment for every `AWS Creates a collection of Amazon API Gateway resources and methods that can be invoked through HTTPS endpoints. -An `AWS::Serverless::HttpApi` resource need not be explicitly added to a AWS Serverless Application Definition template. A resource of this type is implicitly created from the union of [HttpApi](#httpapi) events defined on `AWS::Serverless::Function` resources defined in the template that do not refer to an `AWS::Serverless::HttpApi` resource. An `AWS::Serverless::HttpApi` resource should be used to define and document the API using OpenApi 3.0, which provides more ability to configure the underlying Amazon API Gateway resources. +An `AWS::Serverless::HttpApi` resource need not be explicitly added to a AWS Serverless Application Model template. A resource of this type is implicitly created from the union of [HttpApi](#httpapi) events defined on `AWS::Serverless::Function` resources defined in the template that do not refer to an `AWS::Serverless::HttpApi` resource. An `AWS::Serverless::HttpApi` resource should be used to define and document the API using OpenApi 3.0, which provides more ability to configure the underlying Amazon API Gateway resources. For complete documentation about this new feature and examples, see the [HTTP API SAM Documentation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-httpapi.html) @@ -278,6 +279,14 @@ StageName | `string` | The name of the API stage. If a name is not given, SAM wi DefinitionUri | `string` | [S3 Location Object](#s3-location-object) | S3 URI or location to the Swagger document describing the API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. **Note** Intrinsic functions are not supported in external OpenApi files, instead use DefinitionBody to define OpenApi definition. DefinitionBody | `JSON or YAML Object` | OpenApi specification that describes your API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. Auth | [HTTP API Auth Object](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-httpapi-httpapiauth.html) | Configure authorization to control access to your API Gateway API. +Tags | Map of `string` to `string` | A map (string to string) that specifies the [tags](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-resource-tags.html) to be added to this HTTP API. When the stack is created, SAM will automatically add the following tag: `httpapi:createdBy: SAM`. +AccessLogSettings | [AccessLogSettings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-stage-accesslogsettings.html) | Settings for logging access in a stage. +CorsConfiguration | `boolean` or [CorsConfiguration Object](#cors-configuration-object) | Enable CORS for all your Http APIs. Specify `true` for adding Cors with domain '*' to your Http APIs or specify a dictionary with additional [CorsConfiguration-Object](#cors-configuration-object). SAM adds `x-amazon-apigateway-cors` header in open api definition for your Http API when this property is defined. NOTE: Cors requires SAM to modify your OpenAPI definition. Hence it works only inline OpenAPI defined with `DefinitionBody`. +DefaultRouteSettings | [RouteSettings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-stage-routesettings.html) | The default route settings for this HTTP API. +RouteSettings | [RouteSettings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigatewayv2-stage-routesettings.html) | Per-route route settings for this HTTP API. +Domain | [Domain Configuration Object](#domain-configuration-object) | Configuration settings for custom domains on API. Must contain `DomainName` and `CertificateArn` +StageVariables | Map of `string` to `string` | A map that defines the stage variables for a Stage. Variable names can have alphanumeric and underscore characters, and the values must match [A-Za-z0-9-._~:/?#&=,]+. +FailOnWarnings | `boolean` | Specifies whether to rollback the API creation (true) or not (false) when a warning is encountered. The default value is false. ##### Return values @@ -651,6 +660,8 @@ Path | `string` | Uri path for which this function is invoked. MUST start with ` Method | `string` | HTTP method for which this function is invoked. ApiId | `string` | Identifier of a HttpApi resource which MUST contain an operation with the given path and method. Typically, this is set to [reference](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) an `AWS::Serverless::HttpApi` resource defined in this template. If not defined, a default `AWS::Serverless::HttpApi` resource is created using a generated OpenApi document contains a union of all paths and methods defined by `HttpApi` events defined in this template that do not specify an ApiId. Auth | [Function Auth Object](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-function-httpapifunctionauth.html) | Auth configuration for this specific Api+Path+Method. Useful for overriding the API's `DefaultAuthorizer` setting auth config on an individual path when no `DefaultAuthorizer` is specified. +TimeoutInMillis | `int` | Custom timeout between 50 and 29,000 milliseconds. The default value is 5,000 milliseconds, or 5 seconds for HTTP APIs. +PayloadFormatVersion | `string` | Specify the format version of the payload sent to the Lambda HTTP API integration. If this field is not given, SAM defaults to "2.0". ##### Example: HttpApi event source object @@ -943,6 +954,7 @@ Properties: - [Function Request Model Object](#function-request-model-object) - [Function Request Parameter Object](#function-request-parameter-object) - [Gateway Response Object](#gateway-response-object) +- [CorsConfiguration Object](#cors-configuration-object) #### S3 Location Object @@ -1237,3 +1249,64 @@ Domain: EvaluateTargetHealth: Boolean # optional | default value is false DistributionDomainName: String # OPTIONAL if the EndpointConfiguration is EDGE | Default points to Api Gateway Distribution | Domain name of a [cloudfront distribution](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html) ``` + +#### Cors Configuration Object +Enable and configure CORS for the HttpAPIs. Enabling CORS will allow your Http API to be called from other domains. +It set to `true` SAM adds '*' for the allowed origins. +When CorsConfiguration is set at property level and also in OpenApi, SAM merges them by overriding the header values in OpenApi with the `CorsConfiguration` property values +When intrinsic functions are used either set the CORS configuration as a property or define CORS in OpenApi definition. +Checkout [HTTPAPI Gateway Developer guide](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html) for more details on these values +```yaml +CorsConfiguration: + AllowMethods: Optional. List containing the HTTP methods to allow for the HttpApi. + AllowHeaders: Optional. List of headers to allow. + AllowOrigins: Optional. List of origins to allow. + MaxAge: Optional. Integer containing the number of seconds to cache CORS Preflight request. + # For example, 600 will cache request for 600 seconds. + AllowCredentials: Optional. Boolean indicating whether request is allowed to contain credentials. + ExposeHeaders: Optional. List of allowed headers +``` + +##### Example +```yaml + CorsConfiguration: #true + AllowHeaders: + - "*" + AllowMethods: + - "GET" + AllowOrigins: + - "https://www.example.com" + ExposeHeaders: + - "*" +``` + +### Referable properties of SAM resources +- [AWS::Serverless::Function](#referable-properties-of-serverless-function) +- [AWS::Serverless::Api](#referable-properties-of-serverless-RestApi) +- [AWS::Serverless::HttpApi](#referable-properties-of-serverless-HttpApi) + +#### Referable properties of Serverless Function +Property Name | Reference | LogicalId | Description +---|:---:|---|--- +Alias | `function-logical-id`.Alias | `function-logical-id`Alias`alias-name` | SAM generates an `AWS::Lambda::Alias` resource when `AutoPublishAlias` property is set. This resource can be referenced in intrinsic functions by using the resource logical ID or `function-logical-id`.Alias +Version | `function-logical-id`.Version | `function-logical-id`Version`sha` | SAM generates an `AWS::Lambda::Version` resource when `AutoPublishAlias` property is set. This resource can be referenced in intrinsic functions by using the resource logical ID or `function-logical-id`.Version +DestinationTopic | `function-logical-id`.DestinationTopic |`function-logical-id`EventInvokeConfig`OnSuccess/OnFailure`Topic| SAM auto creates an `AWS::SNS::Topic` resource when `Destination` property of `DestinationConfig` property in `EventInvokeConfig` property is not specified. This generated resource can be referenced by using `function-logical-id`.DestinationTopic +DestinationQueue | `function-logical-id`.DestinationQueue |`function-logical-id`EventInvokeConfig`OnSuccess/OnFailure`Queue | SAM auto creates an `AWS::SQS::Queue` resource when `Destination` property of `DestinationConfig` property in `EventInvokeConfig` property is not specified. This generated resource can be referenced by using `function-logical-id`.DestinationQueue + +#### Referable properties of Serverless RestApi + +Property Name | Reference | LogicalId | Description +---|:---:|---|--- +Stage | `restapi-logical-id`.Stage | `restapi-logical-id` `StageName`Stage | SAM generates `AWS::ApiGateway::Stage` resource when `AWS::Serverless::Api` resource is defined. This resource can be referenced in intrinsic function using the resource logical id or `restapi-logical-id`.Stage +Deployment | `restapi-logical-id`.Deployment | `restapi-logical-id`Deployment`sha` | SAM generates `AWS::ApiGateway::Deployment` resource when `AWS::Serverless::Api` resource is defined. This resource can be referenced in intrinsic function using the resource logical id or `restapi-logical-id`.Deployment +DomainName | `restapi-logical-id`.DomainName | `domain-logical-id` | `AWS::ApiGateway::DomainName` resource can be referenced by using the resource logical id or `restapi-logical-id`.DomainName when `DomainName` resource is defined in `Domain` property of `AWS::Serverless::Api` +UsagePlan | `restapi-logical-id`.UsagePlan | `restapi-logical-id`UsagePlan | SAM generates UsagePlan, UsagePlanKey and ApiKey resources when `UsagePlan` property is set. UsagePlan resource can be referenced in intrinsic function using the resource logical id or `restapi-logical-id`.UsagePlan +UsagePlanKey | `restapi-logical-id`.UsagePlanKey |`restapi-logical-id`UsagePlanKey | SAM generates UsagePlan, UsagePlanKey and ApiKey resources when `UsagePlan` property is set. UsagePlanKey resource can be referenced in intrinsic function using the resource logical id or `restapi-logical-id`.UsagePlanKey +ApiKey | `restapi-logical-id`.ApiKey |`restapi-logical-id`ApiKey | SAM generates UsagePlan, UsagePlanKey and ApiKey resources when `UsagePlan` property is set. ApiKey resource can be referenced in intrinsic function using the resource logical id or `restapi-logical-id`.ApiKey + +#### Referable properties of Serverless HttpApi + +Property Name | Reference | LogicalId | Description +---|:---:|---|--- +Stage | `httpapi-logical-id`.Stage | `httpapi-logical-id`ApiGatewayDefaultStage or `httpapi-logical-id` `StageName`Stage | SAM generates `AWS::ApiGatewayV2::Stage` resource with `httpapi-logical-id`ApiGatewayDefaultStage logical id if `StageName` property is not defined. If an explicit `StageName` property is defined them SAM generates `AWS::ApiGatewayV2::Stage` resource with `httpapi-logical-id` `StageName`Stage logicalId. This resource can be referenced in intrinsic functions using `httpapi-logical-id`.Stage +DomainName | `httpapi-logical-id`.DomainName | `domain-logical-id` | `AWS::ApiGatewayV2::DomainName` resource can be referenced by using the resource logical id or `restapi-logical-id`.DomainName when `DomainName` resource is defined in `Domain` property of `AWS::Serverless::Api` \ No newline at end of file