From bb7a72abf126b1dae9c7a51b8089d9c9eaa9459f Mon Sep 17 00:00:00 2001 From: Chih-Hsuan Yen Date: Wed, 17 Mar 2021 13:37:29 +0800 Subject: [PATCH 01/28] chore: don't install integration tests (#1964) --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 38af9df29b..8ca3f56052 100755 --- a/setup.py +++ b/setup.py @@ -58,7 +58,9 @@ def read_requirements(req="base.txt"): url="https://github.com/awslabs/serverless-application-model", license="Apache License 2.0", # Exclude all but the code folders - packages=find_packages(exclude=("tests", "tests.*", "docs", "examples", "versions")), + packages=find_packages( + exclude=("tests", "tests.*", "integration", "integration.*", "docs", "examples", "versions") + ), install_requires=read_requirements("base.txt"), include_package_data=True, extras_require={"dev": read_requirements("dev.txt")}, From b35be107c166ac484554a97097ef8981b5d9da2c Mon Sep 17 00:00:00 2001 From: Harsh Mishra Date: Wed, 17 Mar 2021 11:07:41 +0530 Subject: [PATCH 02/28] Remove unnecessary use of comprehension (#1805) --- tests/openapi/test_openapi.py | 2 +- tests/swagger/test_swagger.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index f29cb4d47e..99197de9c4 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -292,7 +292,7 @@ def setUp(self): def test_must_iterate_on_paths(self): expected = {"/foo", "/bar", "/baz"} - actual = set([path for path in self.editor.iter_on_path()]) + actual = set(list(self.editor.iter_on_path())) self.assertEqual(expected, actual) diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index 615fabec37..cf61477262 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -302,7 +302,7 @@ def setUp(self): def test_must_iterate_on_paths(self): expected = {"/foo", "/bar", "/baz"} - actual = set([path for path in self.editor.iter_on_path()]) + actual = set(list(self.editor.iter_on_path())) self.assertEqual(expected, actual) From f6914fa616dfde6958ac364e46fa314bf7b6258b Mon Sep 17 00:00:00 2001 From: Pranav <54665036+Pranav016@users.noreply.github.com> Date: Thu, 18 Mar 2021 13:10:05 +0530 Subject: [PATCH 03/28] fix: Grammatical error in README.md (#1965) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09e8c5638f..c2d6f6b70e 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ environment that lets you locally build, test, debug, and deploy applications de ## What is this Github repository? 💻 -This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of examples applications. +This GitHub repository contains the SAM Specification, the Python code that translates SAM templates into AWS CloudFormation stacks and lots of example applications. In the words of SAM developers: > SAM Translator is the Python code that deploys SAM templates via AWS CloudFormation. Source code is high quality (95% unit test coverage), From fa2e1f58749217214ba789e4eb3bb2ceb2246b82 Mon Sep 17 00:00:00 2001 From: Cosh_ Date: Mon, 29 Mar 2021 09:37:25 -0700 Subject: [PATCH 04/28] fix: Added SAR Support Check (#1972) * Added SAR Support Check * Added docstring and Removed Instance Initialization for Class Method --- .../plugins/application/serverless_app_plugin.py | 5 +++++ samtranslator/region_configuration.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 2f4d9b0e4e..02333e0f45 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -12,6 +12,7 @@ from samtranslator.public.sdk.template import SamTemplate from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.intrinsics.actions import FindInMapAction +from samtranslator.region_configuration import RegionConfiguration LOG = logging.getLogger(__name__) @@ -104,6 +105,10 @@ def on_before_transform_template(self, template_dict): if key not in self._applications: try: + if not RegionConfiguration.is_sar_supported(): + raise InvalidResourceException( + logical_id, "Serverless Application Repository is not available in this region." + ) # Lazy initialization of the client- create it when it is needed if not self._sar_client: self._sar_client = boto3.client("serverlessrepo") diff --git a/samtranslator/region_configuration.py b/samtranslator/region_configuration.py index 712d4faf9f..c7c6b55e8d 100644 --- a/samtranslator/region_configuration.py +++ b/samtranslator/region_configuration.py @@ -1,3 +1,5 @@ +import boto3 + from .translator.arn_generator import ArnGenerator @@ -22,3 +24,15 @@ def is_apigw_edge_configuration_supported(cls): "aws-iso-b", "aws-cn", ] + + @classmethod + def is_sar_supported(cls): + """ + SAR is not supported in af-south-1 at the moment. + https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/ + + :return: True, if SAR is supported in current region. + """ + return boto3.Session().region_name not in [ + "af-south-1", + ] From 006c18484869a34968621c80c1c3a476ecd0ff14 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 30 Mar 2021 16:40:34 -0700 Subject: [PATCH 05/28] update pyyaml version to get the security update (#1974) --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index be38513017..193b157bbb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -3,7 +3,7 @@ flake8~=3.8.4 tox~=3.20.1 pytest-cov~=2.10.1 pylint>=1.7.2,<2.0 -pyyaml~=5.3.1 +pyyaml~=5.4 # Test requirements pytest~=6.1.1; python_version >= '3.6' From ffd3b115573ff1b47de4395a962804302d1521a1 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 6 Apr 2021 17:11:12 -0700 Subject: [PATCH 06/28] fix the request parameter parsing, the value can contain dots (#1975) * fix the request parameter parsing, the value can contain dots * fix the unit test for python 2.7 * use built in split, instead of concatenating the string --- samtranslator/swagger/swagger.py | 3 ++- .../input/function_with_request_parameters.yaml | 1 + .../aws-cn/function_with_request_parameters.json | 12 +++++++++--- .../function_with_request_parameters.json | 12 +++++++++--- .../output/function_with_request_parameters.json | 14 ++++++++++---- 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 85b131855b..64e6699b87 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -1134,7 +1134,8 @@ def add_request_parameters_to_method(self, path, method_name, request_parameters parameter_name = request_parameter["Name"] location_name = parameter_name.replace("method.request.", "") - location, name = location_name.split(".") + + location, name = location_name.split(".", 1) if location == "querystring": location = "query" diff --git a/tests/translator/input/function_with_request_parameters.yaml b/tests/translator/input/function_with_request_parameters.yaml index e77a7c4a86..2875457c5a 100644 --- a/tests/translator/input/function_with_request_parameters.yaml +++ b/tests/translator/input/function_with_request_parameters.yaml @@ -38,3 +38,4 @@ Resources: RequestParameters: - method.request.querystring.type - method.request.path.id + - method.request.querystring.full.type diff --git a/tests/translator/output/aws-cn/function_with_request_parameters.json b/tests/translator/output/aws-cn/function_with_request_parameters.json index 9cef0cf7de..d5715a3542 100644 --- a/tests/translator/output/aws-cn/function_with_request_parameters.json +++ b/tests/translator/output/aws-cn/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeploymentc2741b5220": { + "ServerlessRestApiDeployment32042a0513": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: c2741b5220c940a753e3d1e18da6763aaba1c19b", + "Description": "RestApi deployment id: 32042a0513cd1c4e5c14794b306c4de10ca5c4af", "StageName": "Stage" } }, @@ -118,7 +118,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeploymentc2741b5220" + "Ref": "ServerlessRestApiDeployment32042a0513" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -247,6 +247,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } diff --git a/tests/translator/output/aws-us-gov/function_with_request_parameters.json b/tests/translator/output/aws-us-gov/function_with_request_parameters.json index 44ff1d8f5b..67345e559c 100644 --- a/tests/translator/output/aws-us-gov/function_with_request_parameters.json +++ b/tests/translator/output/aws-us-gov/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeployment7c706bcd56": { + "ServerlessRestApiDeploymentbe3a929cf9": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: 7c706bcd56e685afb5882e0219515c9413bcd13b", + "Description": "RestApi deployment id: be3a929cf90555789f2865fc4a96eb9a11ff7a81", "StageName": "Stage" } }, @@ -128,7 +128,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeployment7c706bcd56" + "Ref": "ServerlessRestApiDeploymentbe3a929cf9" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -247,6 +247,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } diff --git a/tests/translator/output/function_with_request_parameters.json b/tests/translator/output/function_with_request_parameters.json index 51dcff6ba7..940a797106 100644 --- a/tests/translator/output/function_with_request_parameters.json +++ b/tests/translator/output/function_with_request_parameters.json @@ -53,13 +53,13 @@ } } }, - "ServerlessRestApiDeployment2223b43914": { + "ServerlessRestApiDeployment104b236830": { "Type": "AWS::ApiGateway::Deployment", "Properties": { "RestApiId": { "Ref": "ServerlessRestApi" }, - "Description": "RestApi deployment id: 2223b439142974b7a3aad1381ddd39027077ce52", + "Description": "RestApi deployment id: 104b236830d26d2515909073d13fa9c58ad6db49", "StageName": "Stage" } }, @@ -118,7 +118,7 @@ "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "ServerlessRestApiDeployment2223b43914" + "Ref": "ServerlessRestApiDeployment104b236830" }, "RestApiId": { "Ref": "ServerlessRestApi" @@ -239,6 +239,12 @@ "type": "string", "name": "id", "in": "path" + }, + { + "required": false, + "type": "string", + "name": "full.type", + "in": "query" } ] } @@ -272,4 +278,4 @@ } } } -} +} \ No newline at end of file From 193c7b7b591e71274da91bb643326d0344cad788 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 12 Apr 2021 18:28:23 +0200 Subject: [PATCH 07/28] Documentation: fix incorrect header (#1979) Fixed the incorrectly formatted header for HTTP API section --- docs/internals/generated_resources.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst index 8b2760d615..ae35e22493 100644 --- a/docs/internals/generated_resources.rst +++ b/docs/internals/generated_resources.rst @@ -129,7 +129,7 @@ AWS::Lambda::Permission MyFunction\ **ThumbnailApi**\ Permission\ **P NOTE: ``ServerlessRestApi*`` resources are generated one per stack. HTTP API -^^^ +^^^^ This is called an "Implicit HTTP API". There can be many functions in the template that define these APIs. Behind the scenes, SAM will collect all implicit HTTP APIs from all Functions in the template, generate an OpenApi doc, and create an implicit ``AWS::Serverless::HttpApi`` using this OpenApi. This API defaults to a StageName called "$default" that cannot be From 6f8311750c38c86bee545ee9d3c7248cf1466f78 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 22 Apr 2021 14:16:42 -0700 Subject: [PATCH 08/28] fix: mutable default values in method definitions (#1997) --- samtranslator/intrinsics/resolver.py | 4 +++- samtranslator/model/apigateway.py | 4 +++- samtranslator/model/intrinsics.py | 4 +++- samtranslator/model/stepfunctions/generators.py | 4 +++- samtranslator/open_api/open_api.py | 4 +++- .../plugins/application/serverless_app_plugin.py | 4 +++- samtranslator/swagger/swagger.py | 4 +++- samtranslator/translator/arn_generator.py | 6 +++--- samtranslator/translator/translator.py | 7 +++++-- tests/intrinsics/test_resolver.py | 5 ----- tests/translator/test_arn_generator.py | 11 ++++------- 11 files changed, 33 insertions(+), 24 deletions(-) diff --git a/samtranslator/intrinsics/resolver.py b/samtranslator/intrinsics/resolver.py index 519f856fe5..af79704a9c 100644 --- a/samtranslator/intrinsics/resolver.py +++ b/samtranslator/intrinsics/resolver.py @@ -8,7 +8,7 @@ class IntrinsicsResolver(object): - def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS): + def __init__(self, parameters, supported_intrinsics=None): """ Instantiate the resolver :param dict parameters: Map of parameter names to their values @@ -17,6 +17,8 @@ def __init__(self, parameters, supported_intrinsics=DEFAULT_SUPPORTED_INTRINSICS :raises TypeError: If parameters or the supported_intrinsics arguments are invalid """ + if supported_intrinsics is None: + supported_intrinsics = DEFAULT_SUPPORTED_INTRINSICS if parameters is None or not isinstance(parameters, dict): raise InvalidDocumentException( [InvalidTemplateException("'Mappings' or 'Parameters' is either null or not a valid dictionary.")] diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 7c91f894e5..d1ea0c2b1d 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -232,8 +232,10 @@ def __init__( function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False, - authorization_scopes=[], + authorization_scopes=None, ): + if authorization_scopes is None: + authorization_scopes = [] if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: raise InvalidResourceException( api_logical_id, diff --git a/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index c81e40d00a..e95a5d6d00 100644 --- a/samtranslator/model/intrinsics.py +++ b/samtranslator/model/intrinsics.py @@ -24,7 +24,9 @@ def fnAnd(argument_list): return {"Fn::And": argument_list} -def make_conditional(condition, true_data, false_data={"Ref": "AWS::NoValue"}): +def make_conditional(condition, true_data, false_data=None): + if false_data is None: + false_data = {"Ref": "AWS::NoValue"} return {"Fn::If": [condition, true_data, false_data]} diff --git a/samtranslator/model/stepfunctions/generators.py b/samtranslator/model/stepfunctions/generators.py index 8e1797c7df..f8431c1895 100644 --- a/samtranslator/model/stepfunctions/generators.py +++ b/samtranslator/model/stepfunctions/generators.py @@ -286,7 +286,7 @@ def _replace_dynamic_values_with_substitutions(self, input): location[path[-1]] = sub_key return substitution_map - def _get_paths_to_intrinsics(self, input, path=[]): + def _get_paths_to_intrinsics(self, input, path=None): """ Returns all paths to dynamic values within a dictionary @@ -294,6 +294,8 @@ def _get_paths_to_intrinsics(self, input, path=[]): :param path: Optional list to keep track of the path to the input dictionary :returns list: List of keys that defines the path to a dynamic value within the input dictionary """ + if path is None: + path = [] dynamic_value_paths = [] if isinstance(input, dict): iterator = input.items() diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index fc0f77a26e..af6b47e49c 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -391,7 +391,7 @@ def add_auth_to_method(self, path, method_name, auth, api): if method_authorizer: self._set_method_authorizer(path, method_name, method_authorizer, authorizers, authorization_scopes) - def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers, authorization_scopes=[]): + def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers, authorization_scopes=None): """ Adds the authorizer_name to the security block for each method on this path. This is used to configure the authorizer for individual functions. @@ -402,6 +402,8 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers authorizers param. :param list authorization_scopes: list of strings that are the auth scopes for this method """ + if authorization_scopes is None: + authorization_scopes = [] normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 02333e0f45..05741c5dfb 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -42,7 +42,7 @@ class ServerlessAppPlugin(BasePlugin): LOCATION_KEY = "Location" TEMPLATE_URL_KEY = "TemplateUrl" - def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False, parameters={}): + def __init__(self, sar_client=None, wait_for_template_active_status=False, validate_only=False, parameters=None): """ Initialize the plugin. @@ -52,6 +52,8 @@ def __init__(self, sar_client=None, wait_for_template_active_status=False, valid :param bool validate_only: Flag to only validate application access (uses get_application API instead) """ super(ServerlessAppPlugin, self).__init__(ServerlessAppPlugin.__name__) + if parameters is None: + parameters = {} self._applications = {} self._in_progress_templates = [] self._sar_client = sar_client diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 64e6699b87..4345373c0e 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -698,7 +698,7 @@ def add_auth_to_method(self, path, method_name, auth, api): if method_apikey_required is not None: self._set_method_apikey_handling(path, method_name, method_apikey_required) - def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers={}, method_scopes=None): + def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers=None, method_scopes=None): """ Adds the authorizer_name to the security block for each method on this path. This is used to configure the authorizer for individual functions. @@ -708,6 +708,8 @@ def _set_method_authorizer(self, path, method_name, authorizer_name, authorizers :param string authorizer_name: Name of the authorizer to use. Must be a key in the authorizers param. """ + if authorizers is None: + authorizers = {} normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 9394a6c0ae..897661e8df 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -6,7 +6,7 @@ class NoRegionFound(Exception): class ArnGenerator(object): - class_boto_session = None + BOTO_SESSION_REGION_NAME = None @classmethod def generate_arn(cls, partition, service, resource, include_account_id=True): @@ -50,10 +50,10 @@ def get_partition_name(cls, region=None): # Use Boto3 to get the region where code is running. This uses Boto's regular region resolution # mechanism, starting from AWS_DEFAULT_REGION environment variable. - if ArnGenerator.class_boto_session is None: + if ArnGenerator.BOTO_SESSION_REGION_NAME is None: region = boto3.session.Session().region_name else: - region = ArnGenerator.class_boto_session.region_name + region = ArnGenerator.BOTO_SESSION_REGION_NAME # If region is still None, then we could not find the region. This will only happen # in the local context. When this is deployed, we will be able to find the region like diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index 99e18e80fe..a1c903b23c 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -45,7 +45,8 @@ def __init__(self, managed_policy_map, sam_parser, plugins=None, boto_session=No self.feature_toggle = None self.boto_session = boto_session - ArnGenerator.class_boto_session = self.boto_session + if self.boto_session: + ArnGenerator.BOTO_SESSION_REGION_NAME = self.boto_session.region_name def _get_function_names(self, resource_dict, intrinsics_resolver): """ @@ -230,7 +231,7 @@ def _get_resources_to_iterate(self, sam_template, macro_resolver): return functions + statemachines + apis + others -def prepare_plugins(plugins, parameters={}): +def prepare_plugins(plugins, parameters=None): """ Creates & returns a plugins object with the given list of plugins installed. In addition to the given plugins, we will also install a few "required" plugins that are necessary to provide complete support for SAM template spec. @@ -240,6 +241,8 @@ def prepare_plugins(plugins, parameters={}): :return samtranslator.plugins.SamPlugins: Instance of `SamPlugins` """ + if parameters is None: + parameters = {} required_plugins = [ DefaultDefinitionBodyPlugin(), make_implicit_rest_api_plugin(), diff --git a/tests/intrinsics/test_resolver.py b/tests/intrinsics/test_resolver.py index 2f62b510f0..946307b872 100644 --- a/tests/intrinsics/test_resolver.py +++ b/tests/intrinsics/test_resolver.py @@ -192,11 +192,6 @@ class SomeAction(Action): with self.assertRaises(TypeError): IntrinsicsResolver({}, supported_intrinsics) - def test_configure_supported_intrinsics_must_error_for_none_input(self): - - with self.assertRaises(TypeError): - IntrinsicsResolver({}, None) - def test_configure_supported_intrinsics_must_error_for_non_dict_input(self): with self.assertRaises(TypeError): diff --git a/tests/translator/test_arn_generator.py b/tests/translator/test_arn_generator.py index 461840cdf4..200f904142 100644 --- a/tests/translator/test_arn_generator.py +++ b/tests/translator/test_arn_generator.py @@ -1,13 +1,13 @@ from unittest import TestCase from parameterized import parameterized -from mock import Mock, patch +from mock import patch from samtranslator.translator.arn_generator import ArnGenerator, NoRegionFound class TestArnGenerator(TestCase): def setUp(self): - ArnGenerator.class_boto_session = None + ArnGenerator.BOTO_SESSION_REGION_NAME = None @parameterized.expand( [("us-east-1", "aws"), ("cn-east-1", "aws-cn"), ("us-gov-west-1", "aws-us-gov"), ("US-EAST-1", "aws")] @@ -23,13 +23,10 @@ def test_get_partition_name_raise_NoRegionFound(self): ArnGenerator.get_partition_name(None) def test_get_partition_name_from_boto_session(self): - boto_session_mock = Mock() - boto_session_mock.region_name = "us-east-1" - - ArnGenerator.class_boto_session = boto_session_mock + ArnGenerator.BOTO_SESSION_REGION_NAME = "us-east-1" actual = ArnGenerator.get_partition_name() self.assertEqual(actual, "aws") - ArnGenerator.class_boto_session = None + ArnGenerator.BOTO_SESSION_REGION_NAME = None From 2925701e23a7af2fb976d0142c81da4268a09803 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 22 Apr 2021 14:23:41 -0700 Subject: [PATCH 09/28] fix: remove explicit logging level set in single module (#1998) --- samtranslator/model/api/api_generator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 91ef473054..d85404b7b0 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -27,7 +27,6 @@ from samtranslator.model.tags.resource_tagging import get_tag_list LOG = logging.getLogger(__name__) -LOG.setLevel(logging.INFO) _CORS_WILDCARD = "'*'" CorsProperties = namedtuple( From ff86ef6f3f43804cccc4454add676a69c34b7afc Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Mon, 10 May 2021 11:41:14 -0500 Subject: [PATCH 10/28] fix: Fail when Intrinsics are in SourceVPC list instead of IntrinsicsSourceVPC (#1999) --- samtranslator/swagger/swagger.py | 71 ++++++++++++------- tests/swagger/test_swagger.py | 12 ++++ ...rror_api_invalid_source_vpc_blacklist.yaml | 31 ++++++++ ...rror_api_invalid_source_vpc_whitelist.yaml | 31 ++++++++ ...rror_api_invalid_source_vpc_blacklist.json | 3 + ...rror_api_invalid_source_vpc_whitelist.json | 3 + 6 files changed, 127 insertions(+), 24 deletions(-) create mode 100644 tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml create mode 100644 tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml create mode 100644 tests/translator/output/error_api_invalid_source_vpc_blacklist.json create mode 100644 tests/translator/output/error_api_invalid_source_vpc_whitelist.json diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 4345373c0e..53ac00bee2 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -879,6 +879,8 @@ def add_resource_policy(self, resource_policy, path, api_id, stage): ip_range_blacklist = resource_policy.get("IpRangeBlacklist") source_vpc_whitelist = resource_policy.get("SourceVpcWhitelist") source_vpc_blacklist = resource_policy.get("SourceVpcBlacklist") + + # Intrinsic's supported in these properties source_vpc_intrinsic_whitelist = resource_policy.get("IntrinsicVpcWhitelist") source_vpce_intrinsic_whitelist = resource_policy.get("IntrinsicVpceWhitelist") source_vpc_intrinsic_blacklist = resource_policy.get("IntrinsicVpcBlacklist") @@ -900,31 +902,38 @@ def add_resource_policy(self, resource_policy, path, api_id, stage): resource_list = self._get_method_path_uri_list(path, api_id, stage) self._add_ip_resource_policy_for_method(ip_range_blacklist, "IpAddress", resource_list) - if ( - (source_vpc_blacklist is not None) - or (source_vpc_intrinsic_blacklist is not None) - or (source_vpce_intrinsic_blacklist is not None) - ): - blacklist_dict = { - "StringEndpointList": source_vpc_blacklist, - "IntrinsicVpcList": source_vpc_intrinsic_blacklist, - "IntrinsicVpceList": source_vpce_intrinsic_blacklist, - } - resource_list = self._get_method_path_uri_list(path, api_id, stage) - self._add_vpc_resource_policy_for_method(blacklist_dict, "StringEquals", resource_list) + if not SwaggerEditor._validate_list_property_is_resolved(source_vpc_blacklist): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "SourceVpcBlacklist must be a list of strings. Use IntrinsicVpcBlacklist instead for values that use Intrinsic Functions" + ) + ] + ) - if ( - (source_vpc_whitelist is not None) - or (source_vpc_intrinsic_whitelist is not None) - or (source_vpce_intrinsic_whitelist is not None) - ): - whitelist_dict = { - "StringEndpointList": source_vpc_whitelist, - "IntrinsicVpcList": source_vpc_intrinsic_whitelist, - "IntrinsicVpceList": source_vpce_intrinsic_whitelist, - } - resource_list = self._get_method_path_uri_list(path, api_id, stage) - self._add_vpc_resource_policy_for_method(whitelist_dict, "StringNotEquals", resource_list) + blacklist_dict = { + "StringEndpointList": source_vpc_blacklist, + "IntrinsicVpcList": source_vpc_intrinsic_blacklist, + "IntrinsicVpceList": source_vpce_intrinsic_blacklist, + } + resource_list = self._get_method_path_uri_list(path, api_id, stage) + self._add_vpc_resource_policy_for_method(blacklist_dict, "StringEquals", resource_list) + + if not SwaggerEditor._validate_list_property_is_resolved(source_vpc_whitelist): + raise InvalidDocumentException( + [ + InvalidTemplateException( + "SourceVpcWhitelist must be a list of strings. Use IntrinsicVpcWhitelist instead for values that use Intrinsic Functions" + ) + ] + ) + + whitelist_dict = { + "StringEndpointList": source_vpc_whitelist, + "IntrinsicVpcList": source_vpc_intrinsic_whitelist, + "IntrinsicVpceList": source_vpce_intrinsic_whitelist, + } + self._add_vpc_resource_policy_for_method(whitelist_dict, "StringNotEquals", resource_list) self._doc[self._X_APIGW_POLICY] = self.resource_policy @@ -1255,3 +1264,17 @@ def safe_compare_regex_with_string(regex, data): def get_path_without_trailing_slash(path): # convert greedy paths to such as {greedy+}, {proxy+} to "*" return re.sub(r"{([a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+\+|proxy\+)}", "*", path) + + @staticmethod + def _validate_list_property_is_resolved(property_list): + """ + Validate if the values of a Property List are all of type string + + :param property_list: Value of a Property List + :return bool: True if the property_list is all of type string otherwise False + """ + + if property_list is not None and not all(isinstance(x, string_types) for x in property_list): + return False + + return True diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index cf61477262..f237aa1039 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -1181,6 +1181,18 @@ def test_must_add_vpc_allow_string_only(self): self.assertEqual(deep_sort_lists(expected), deep_sort_lists(self.editor.swagger[_X_POLICY])) + @parameterized.expand( + [ + param("SourceVpcWhitelist"), + param("SourceVpcBlacklist"), + ] + ) + def test_must_fail_when_vpc_whitelist_is_non_string(self, resource_policy_key): + resource_policy = {resource_policy_key: [{"sub": "somevalue"}]} + + with self.assertRaises(InvalidDocumentException): + self.editor.add_resource_policy(resource_policy, "/foo", "123", "prod") + def test_must_add_vpc_deny_string_only(self): resourcePolicy = { diff --git a/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml b/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml new file mode 100644 index 0000000000..27eadd1fed --- /dev/null +++ b/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml @@ -0,0 +1,31 @@ +Globals: + Api: + Auth: + ResourcePolicy: + SourceVpcBlacklist: [{"Ref":"SomeParameter"}] + +Parameters: + SomeParameter: + Type: String + Default: param + +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: + Api: + Type: Api + Properties: + Method: Put + Path: /get \ No newline at end of file diff --git a/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml b/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml new file mode 100644 index 0000000000..99ba49f2d7 --- /dev/null +++ b/tests/translator/input/error_api_invalid_source_vpc_whitelist.yaml @@ -0,0 +1,31 @@ +Globals: + Api: + Auth: + ResourcePolicy: + SourceVpcWhitelist: [{"Ref":"SomeParameter"}] + +Parameters: + SomeParameter: + Type: String + Default: param + +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: + Api: + Type: Api + Properties: + Method: Put + Path: /get \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_source_vpc_blacklist.json b/tests/translator/output/error_api_invalid_source_vpc_blacklist.json new file mode 100644 index 0000000000..cc7d2188a8 --- /dev/null +++ b/tests/translator/output/error_api_invalid_source_vpc_blacklist.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. SourceVpcBlacklist must be a list of strings. Use IntrinsicVpcBlacklist instead for values that use Intrinsic Functions" +} diff --git a/tests/translator/output/error_api_invalid_source_vpc_whitelist.json b/tests/translator/output/error_api_invalid_source_vpc_whitelist.json new file mode 100644 index 0000000000..5ce6cf3fd7 --- /dev/null +++ b/tests/translator/output/error_api_invalid_source_vpc_whitelist.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. SourceVpcWhitelist must be a list of strings. Use IntrinsicVpcWhitelist instead for values that use Intrinsic Functions" +} From 4f272ae61fb073f0913f7f735a137f8b0f817a10 Mon Sep 17 00:00:00 2001 From: Tarun Date: Wed, 23 Jun 2021 15:34:49 -0700 Subject: [PATCH 11/28] feat: Adding support for metric publishing. (#2062) * Adding support for metric publishing, manually tested and tests pending. * Adding make action for running test with html coverage. * Adding unit tests for metrics. * Writing integration tests for metrics publishing. * Black reformatting * Fixing unit test. * Addressing PR comments. * Clearing __init__py from test module. * Updating documentation for publish method. Co-authored-by: Tarun Mall --- Makefile | 3 + integration/metrics/__init__.py | 0 .../metrics/test_metrics_integration.py | 84 ++++++++ samtranslator/metrics/__init__.py | 0 samtranslator/metrics/metrics.py | 158 +++++++++++++++ samtranslator/translator/translator.py | 5 +- tests/metrics/test_metrics.py | 191 ++++++++++++++++++ 7 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 integration/metrics/__init__.py create mode 100644 integration/metrics/test_metrics_integration.py create mode 100644 samtranslator/metrics/__init__.py create mode 100644 samtranslator/metrics/metrics.py create mode 100644 tests/metrics/test_metrics.py diff --git a/Makefile b/Makefile index 09dce252d5..9675b8ccb0 100755 --- a/Makefile +++ b/Makefile @@ -8,6 +8,9 @@ init: test: pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests/* +test-cov-report: + pytest --cov samtranslator --cov-report term-missing --cov-report html --cov-fail-under 95 tests/* + integ-test: pytest --no-cov integration/* diff --git a/integration/metrics/__init__.py b/integration/metrics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/metrics/test_metrics_integration.py b/integration/metrics/test_metrics_integration.py new file mode 100644 index 0000000000..e2a69b757a --- /dev/null +++ b/integration/metrics/test_metrics_integration.py @@ -0,0 +1,84 @@ +import boto3 +import time +import uuid +from datetime import datetime, timedelta +from unittest import TestCase +from samtranslator.metrics.metrics import ( + Metrics, + CWMetricsPublisher, +) + + +class MetricsIntegrationTest(TestCase): + """ + This class will use a unique metric namespace to create metrics. There is no cleanup done here + because if a particular namespace is unsed for 2 weeks it'll be cleanedup by cloudwatch. + """ + + @classmethod + def setUpClass(cls): + cls.cw_client = boto3.client("cloudwatch") + cls.cw_metric_publisher = CWMetricsPublisher(cls.cw_client) + + def test_publish_single_metric(self): + now = datetime.now() + tomorrow = now + timedelta(days=1) + namespace = self.get_unique_namespace() + metrics = Metrics(namespace, CWMetricsPublisher(self.cw_client)) + dimensions = [{"Name": "Dim1", "Value": "Val1"}, {"Name": "Dim2", "Value": "Val2"}] + metrics.record_count("TestCountMetric", 1, dimensions=dimensions) + metrics.record_count("TestCountMetric", 3, dimensions=dimensions) + metrics.record_latency("TestLatencyMetric", 1200, dimensions=dimensions) + metrics.record_latency("TestLatencyMetric", 1600, dimensions=dimensions) + metrics.publish() + total_count = self.get_metric_data( + namespace, + "TestCountMetric", + dimensions, + datetime(now.year, now.month, now.day), + datetime(tomorrow.year, tomorrow.month, tomorrow.day), + ) + latency_avg = self.get_metric_data( + namespace, + "TestLatencyMetric", + dimensions, + datetime(now.year, now.month, now.day), + datetime(tomorrow.year, tomorrow.month, tomorrow.day), + stat="Average", + ) + + self.assertEqual(total_count[0], 1 + 3) + self.assertEqual(latency_avg[0], 1400) + + def get_unique_namespace(self): + namespace = "SinglePublishTest-{}".format(uuid.uuid1()) + while True: + response = self.cw_client.list_metrics(Namespace=namespace) + if not response["Metrics"]: + return namespace + namespace = "SinglePublishTest-{}".format(uuid.uuid1()) + + def get_metric_data(self, namespace, metric_name, dimensions, start_time, end_time, stat="Sum"): + retries = 3 + while retries > 0: + retries -= 1 + response = self.cw_client.get_metric_data( + MetricDataQueries=[ + { + "Id": namespace.replace("-", "_").lower(), + "MetricStat": { + "Metric": {"Namespace": namespace, "MetricName": metric_name, "Dimensions": dimensions}, + "Period": 60, + "Stat": stat, + }, + } + ], + StartTime=start_time, + EndTime=end_time, + ) + values = response["MetricDataResults"][0]["Values"] + if values: + return values + print("No values found by for metric: {}. Waiting for 5 seconds...".format(metric_name)) + time.sleep(5) + return [0] diff --git a/samtranslator/metrics/__init__.py b/samtranslator/metrics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samtranslator/metrics/metrics.py b/samtranslator/metrics/metrics.py new file mode 100644 index 0000000000..42e83808cd --- /dev/null +++ b/samtranslator/metrics/metrics.py @@ -0,0 +1,158 @@ +""" +Helper classes to publish metrics +""" +import logging + +LOG = logging.getLogger(__name__) + + +class MetricsPublisher: + """Interface for all MetricPublishers""" + + def __init__(self): + pass + + def publish(self, namespace, metrics): + raise NotImplementedError + + +class CWMetricsPublisher(MetricsPublisher): + BATCH_SIZE = 20 + + def __init__(self, cloudwatch_client): + """ + Constructor + + :param cloudwatch_client: cloudwatch client required to publish metrics to cloudwatch + """ + MetricsPublisher.__init__(self) + self.cloudwatch_client = cloudwatch_client + + def publish(self, namespace, metrics): + """ + Method to publish all metrics to Cloudwatch. + + :param namespace: namespace applied to all metrics published. + :param metrics: list of metrics to be published + """ + batch = [] + for metric in metrics: + batch.append(metric) + # Cloudwatch recommends not to send more than 20 metrics at a time + if len(batch) == self.BATCH_SIZE: + self._flush_metrics(namespace, batch) + batch = [] + self._flush_metrics(namespace, batch) + + def _flush_metrics(self, namespace, metrics): + """ + Internal method to publish all provided metrics to cloudwatch, please make sure that array size of metrics is <= 20. + """ + metric_data = list(map(lambda m: m.get_metric_data(), metrics)) + try: + self.cloudwatch_client.put_metric_data(Namespace=namespace, MetricData=metric_data) + except Exception as e: + LOG.exception("Failed to report {} metrics".format(len(metric_data)), exc_info=e) + + +class DummyMetricsPublisher(MetricsPublisher): + def __init__(self): + MetricsPublisher.__init__(self) + + def publish(self, namespace, metrics): + """Do not publish any metric, this is a dummy publisher used for offline use.""" + LOG.debug("Dummy publisher ignoring {} metrices".format(len(metrics))) + + +class Unit: + Seconds = "Seconds" + Microseconds = "Microseconds" + Milliseconds = "Milliseconds" + Bytes = "Bytes" + Kilobytes = "Kilobytes" + Megabytes = "Megabytes" + Bits = "Bits" + Kilobits = "Kilobits" + Megabits = "Megabits" + Percent = "Percent" + Count = "Count" + + +class MetricDatum: + """ + Class to hold Metric data. + """ + + def __init__(self, name, value, unit, dimensions=None): + """ + Constructor + + :param name: metric name + :param value: value of metric + :param unit: unit of metric (try using values from Unit class) + :param dimensions: array of dimensions applied to the metric + """ + self.name = name + self.value = value + self.unit = unit + self.dimensions = dimensions if dimensions else [] + + def get_metric_data(self): + return {"MetricName": self.name, "Value": self.value, "Unit": self.unit, "Dimensions": self.dimensions} + + +class Metrics: + def __init__(self, namespace="ServerlessTransform", metrics_publisher=None): + """ + Constructor + + :param namespace: namespace under which all metrics will be published + :param metrics_publisher: publisher to publish all metrics + """ + self.metrics_publisher = metrics_publisher if metrics_publisher else DummyMetricsPublisher() + self.metrics_cache = [] + self.namespace = namespace + + def __del__(self): + if len(self.metrics_cache) > 0: + # attempting to publish if user forgot to call publish in code + LOG.warn("There are unpublished metrics. Please make sure you call publish after you record all metrics.") + self.publish() + + def _record_metric(self, name, value, unit, dimensions=[]): + """ + Create and save metric objects to an array. + + :param name: metric name + :param value: value of metric + :param unit: unit of metric (try using values from Unit class) + :param dimensions: array of dimensions applied to the metric + """ + self.metrics_cache.append(MetricDatum(name, value, unit, dimensions)) + + def record_count(self, name, value, dimensions=[]): + """ + Create metric with unit Count. + + :param name: metric name + :param value: value of metric + :param unit: unit of metric (try using values from Unit class) + :param dimensions: array of dimensions applied to the metric + """ + self._record_metric(name, value, Unit.Count, dimensions) + + def record_latency(self, name, value, dimensions=[]): + """ + Create metric with unit Milliseconds. + + :param name: metric name + :param value: value of metric + :param unit: unit of metric (try using values from Unit class) + :param dimensions: array of dimensions applied to the metric + """ + self._record_metric(name, value, Unit.Milliseconds, dimensions) + + def publish(self): + """Calls publish method from the configured metrics publisher to publish metrics""" + self.metrics_publisher.publish(self.namespace, self.metrics_cache) + self.metrics_cache = [] diff --git a/samtranslator/translator/translator.py b/samtranslator/translator/translator.py index a1c903b23c..22ea67006d 100644 --- a/samtranslator/translator/translator.py +++ b/samtranslator/translator/translator.py @@ -1,8 +1,8 @@ import copy +from samtranslator.metrics.metrics import DummyMetricsPublisher, Metrics from samtranslator.feature_toggle.feature_toggle import ( FeatureToggle, - FeatureToggleLocalConfigProvider, FeatureToggleDefaultConfigProvider, ) from samtranslator.model import ResourceTypeResolver, sam_resources @@ -32,7 +32,7 @@ class Translator: """Translates SAM templates into CloudFormation templates""" - def __init__(self, managed_policy_map, sam_parser, plugins=None, boto_session=None): + def __init__(self, managed_policy_map, sam_parser, plugins=None, boto_session=None, metrics=None): """ :param dict managed_policy_map: Map of managed policy names to the ARNs :param sam_parser: Instance of a SAM Parser @@ -44,6 +44,7 @@ def __init__(self, managed_policy_map, sam_parser, plugins=None, boto_session=No self.sam_parser = sam_parser self.feature_toggle = None self.boto_session = boto_session + self.metrics = metrics if metrics else Metrics("ServerlessTransform", DummyMetricsPublisher()) if self.boto_session: ArnGenerator.BOTO_SESSION_REGION_NAME = self.boto_session.region_name diff --git a/tests/metrics/test_metrics.py b/tests/metrics/test_metrics.py new file mode 100644 index 0000000000..52165f4cfc --- /dev/null +++ b/tests/metrics/test_metrics.py @@ -0,0 +1,191 @@ +from parameterized import parameterized, param +from unittest import TestCase +from mock import MagicMock +from samtranslator.metrics.metrics import ( + Metrics, + MetricsPublisher, + CWMetricsPublisher, + DummyMetricsPublisher, + Unit, + MetricDatum, +) + + +class MetricPublisherTestHelper(MetricsPublisher): + def __init__(self): + MetricsPublisher.__init__(self) + self.metrics_cache = [] + self.namespace = "" + + def publish(self, namespace, metrics): + self.namespace = namespace + self.metrics_cache = metrics + + +class TestMetrics(TestCase): + @parameterized.expand( + [ + param( + "DummyNamespace", + "CountMetric", + 12, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "IAMError", + 59, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + ] + ) + def test_publishing_count_metric(self, namespace, name, value, dimensions): + mock_metrics_publisher = MetricPublisherTestHelper() + metrics = Metrics(namespace, mock_metrics_publisher) + metrics.record_count(name, value, dimensions) + metrics.publish() + self.assertEqual(len(mock_metrics_publisher.metrics_cache), 1) + published_metric = mock_metrics_publisher.metrics_cache[0].get_metric_data() + self.assertEqual(published_metric["MetricName"], name) + self.assertEqual(published_metric["Dimensions"], dimensions) + self.assertEqual(published_metric["Value"], value) + + @parameterized.expand( + [ + param( + "DummyNamespace", + "SARLatency", + 1200, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "IAMLatency", + 400, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + ] + ) + def test_publishing_latency_metric(self, namespace, name, value, dimensions): + mock_metrics_publisher = MetricPublisherTestHelper() + metrics = Metrics(namespace, mock_metrics_publisher) + metrics.record_latency(name, value, dimensions) + metrics.publish() + self.assertEqual(len(mock_metrics_publisher.metrics_cache), 1) + published_metric = mock_metrics_publisher.metrics_cache[0].get_metric_data() + self.assertEqual(published_metric["MetricName"], name) + self.assertEqual(published_metric["Dimensions"], dimensions) + self.assertEqual(published_metric["Value"], value) + + @parameterized.expand( + [ + param( + "DummyNamespace", + "CountMetric", + 12, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "LatencyMetric", + 1200, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + ] + ) + def test_publishing_metric_without_calling_publish(self, namespace, name, value, dimensions): + mock_metrics_publisher = MetricPublisherTestHelper() + metrics = Metrics(namespace, mock_metrics_publisher) + metrics.record_count(name, value, dimensions) + del metrics + self.assertEqual(len(mock_metrics_publisher.metrics_cache), 1) + published_metric = mock_metrics_publisher.metrics_cache[0].get_metric_data() + self.assertEqual(published_metric["MetricName"], name) + self.assertEqual(published_metric["Dimensions"], dimensions) + self.assertEqual(published_metric["Value"], value) + + +class TestCWMetricPublisher(TestCase): + @parameterized.expand( + [ + param( + "DummyNamespace", + "CountMetric", + 12, + Unit.Count, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "IAMError", + 59, + Unit.Count, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "SARLatency", + 1200, + Unit.Milliseconds, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + param( + "DummyNamespace", + "IAMLatency", + 400, + Unit.Milliseconds, + [{"Name": "SAM", "Value": "Dim1"}, {"Name": "SAM", "Value": "Dim2"}], + ), + ] + ) + def test_publish_metric(self, namespace, name, value, unit, dimensions): + mock_cw_client = MagicMock() + metric_publisher = CWMetricsPublisher(mock_cw_client) + metric_datum = MetricDatum(name, value, unit, dimensions) + metrics = [metric_datum] + metric_publisher.publish(namespace, metrics) + call_kwargs = mock_cw_client.put_metric_data.call_args.kwargs + published_metric_data = call_kwargs["MetricData"][0] + self.assertEqual(call_kwargs["Namespace"], namespace) + self.assertEqual(published_metric_data["MetricName"], name) + self.assertEqual(published_metric_data["Unit"], unit) + self.assertEqual(published_metric_data["Value"], value) + self.assertEqual(published_metric_data["Dimensions"], dimensions) + + @parameterized.expand( + [ + param("DummyNamespace", "CountMetric", 12, Unit.Count, []), + ] + ) + def test_publish_more_than_20_metrics(self, namespace, name, value, unit, dimensions): + mock_cw_client = MagicMock() + metric_publisher = CWMetricsPublisher(mock_cw_client) + single_metric = MetricDatum(name, value, unit, dimensions) + metrics_list = [] + total_metrics = 45 + for i in range(total_metrics): + metrics_list.append(single_metric) + metric_publisher.publish(namespace, metrics_list) + call_args_list = mock_cw_client.put_metric_data.call_args_list + + self.assertEqual(mock_cw_client.put_metric_data.call_count, 3) + # Validating that metrics are published in batches of 20 + self.assertEqual(len(call_args_list[0].kwargs["MetricData"]), min(total_metrics, metric_publisher.BATCH_SIZE)) + total_metrics -= metric_publisher.BATCH_SIZE + self.assertEqual(len(call_args_list[1].kwargs["MetricData"]), min(total_metrics, metric_publisher.BATCH_SIZE)) + total_metrics -= metric_publisher.BATCH_SIZE + self.assertEqual(len(call_args_list[2].kwargs["MetricData"]), min(total_metrics, metric_publisher.BATCH_SIZE)) + + def test_do_not_fail_on_cloudwatch_any_exception(self): + mock_cw_client = MagicMock() + mock_cw_client.put_metric_data = MagicMock() + mock_cw_client.put_metric_data.side_effect = Exception("BOOM FAILED!!") + metric_publisher = CWMetricsPublisher(mock_cw_client) + single_metric = MetricDatum("Name", 20, Unit.Count, []) + metric_publisher.publish("SomeNamespace", [single_metric]) + self.assertTrue(True) + + def test_for_code_coverage(self): + dummy_publisher = DummyMetricsPublisher() + dummy_publisher.publish("NS", [None]) + self.assertTrue(True) From 36b7a3d95286a70a44c590ef5c45e687da42d969 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Thu, 24 Jun 2021 13:09:40 -0700 Subject: [PATCH 12/28] Update AppConfig boto3 client config to shorten timeout (#2070) * Update AppConfig boto3 client config to shorten timeout * Update timeout config of AppConfig boto3 client --- samtranslator/feature_toggle/feature_toggle.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/samtranslator/feature_toggle/feature_toggle.py b/samtranslator/feature_toggle/feature_toggle.py index 2eddcfa1eb..cbfcf2a3cd 100644 --- a/samtranslator/feature_toggle/feature_toggle.py +++ b/samtranslator/feature_toggle/feature_toggle.py @@ -135,8 +135,9 @@ def __init__(self, application_id, environment_id, configuration_profile_id): try: LOG.info("Loading feature toggle config from AppConfig...") # Lambda function has 120 seconds limit - # (5 + 25) * 2, 60 seconds maximum timeout duration - client_config = Config(connect_timeout=5, read_timeout=25, retries={"total_max_attempts": 2}) + # (5 + 5) * 2, 20 seconds maximum timeout duration + # In case of high latency from AppConfig, we can always fall back to use an empty config and continue transform + client_config = Config(connect_timeout=5, read_timeout=5, retries={"total_max_attempts": 2}) self.app_config_client = boto3.client("appconfig", config=client_config) response = self.app_config_client.get_configuration( Application=application_id, From bb101ef2b335bbbdb5cfd560c256bd375b994843 Mon Sep 17 00:00:00 2001 From: Rondineli Date: Wed, 30 Jun 2021 19:43:43 +0100 Subject: [PATCH 13/28] feat: Add ValidateBody/ValidateParameters attribute to API models (#2026) * Added validators to the swagger definitions when a model is required * Remove approach of trying an extra field for validators * WIP - tests deps not working * Finished and fixed all tests to make pr command was complaining. Fixed case where a validator could have a / only and changed it to translate as 'root' * py2.7 complaint * Fix py2.7 order of validators on output folder * WIP - adding validator fields * Set another strategy to set the validators without affect existent clients with model required * Set another strategy to set the validators without affect existent clients with model required * Remove mistaken file pushed * Remove mistaken file pushed * Partial coments fixes * Implemented case where we can set a only limited combinations on the specs * Fix case test when multiple path are using same validator * Added openapi3 specific tests * Added openapi3 specific tests * Fix requested changes from review: Renamed from ValidateRequest -> ValidateParameters also renamed everything from validator_request. Moved validators names according with the comum use. * new approach to not cause KeyErrors * Setting validator to the only given method not all methods on the path * removed extra space on comment - lint * Normalized method name path that comes from the template Co-authored-by: Rondineli Gomes de Araujo Co-authored-by: Rondineli Co-authored-by: Rondineli --- samtranslator/model/eventsources/push.py | 29 + samtranslator/swagger/swagger.py | 62 ++ tests/swagger/test_swagger.py | 112 +++ .../api_request_model_with_validator.yaml | 157 ++++ ...equest_model_with_validator_openapi_3.yaml | 32 + ...quest_model_with_intrinsics_validator.yaml | 88 ++ ..._request_model_with_strings_validator.yaml | 38 + .../api_request_model_with_validator.json | 829 +++++++++++++++++ ...equest_model_with_validator_openapi_3.json | 154 ++++ .../api_request_model_with_validator.json | 837 ++++++++++++++++++ ...equest_model_with_validator_openapi_3.json | 162 ++++ .../api_request_model_with_validator.json | 837 ++++++++++++++++++ ...equest_model_with_validator_openapi_3.json | 162 ++++ ...quest_model_with_intrinsics_validator.json | 3 + ..._request_model_with_strings_validator.json | 3 + tests/translator/test_translator.py | 4 + 16 files changed, 3509 insertions(+) create mode 100644 tests/translator/input/api_request_model_with_validator.yaml create mode 100644 tests/translator/input/api_request_model_with_validator_openapi_3.yaml create mode 100644 tests/translator/input/error_api_request_model_with_intrinsics_validator.yaml create mode 100644 tests/translator/input/error_api_request_model_with_strings_validator.yaml create mode 100644 tests/translator/output/api_request_model_with_validator.json create mode 100644 tests/translator/output/api_request_model_with_validator_openapi_3.json create mode 100644 tests/translator/output/aws-cn/api_request_model_with_validator.json create mode 100644 tests/translator/output/aws-cn/api_request_model_with_validator_openapi_3.json create mode 100644 tests/translator/output/aws-us-gov/api_request_model_with_validator.json create mode 100644 tests/translator/output/aws-us-gov/api_request_model_with_validator_openapi_3.json create mode 100644 tests/translator/output/error_api_request_model_with_intrinsics_validator.json create mode 100644 tests/translator/output/error_api_request_model_with_strings_validator.json diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index de0f996379..73b1df299d 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -788,6 +788,35 @@ def _add_swagger_integration(self, api, function, intrinsics_resolver): path=self.Path, method_name=self.Method, request_model=self.RequestModel ) + validate_body = self.RequestModel.get("ValidateBody") + validate_parameters = self.RequestModel.get("ValidateParameters") + + # Checking if any of the fields are defined as it can be false we are checking if the field are not None + if validate_body is not None or validate_parameters is not None: + + # as we are setting two different fields we are here setting as default False + # In case one of them are not defined + validate_body = False if validate_body is None else validate_body + validate_parameters = False if validate_parameters is None else validate_parameters + + # If not type None but any other type it should explicitly invalidate the Spec + # Those fields should be only a boolean + if not isinstance(validate_body, bool) or not isinstance(validate_parameters, bool): + raise InvalidEventException( + self.relative_id, + "Unable to set Validator to RequestModel [{model}] on API method [{method}] for path [{path}] " + "ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported.".format( + model=method_model, method=self.Method, path=self.Path + ), + ) + + editor.add_request_validator_to_method( + path=self.Path, + method_name=self.Method, + validate_body=validate_body, + validate_parameters=validate_parameters, + ) + if self.RequestParameters: default_value = {"Required": False, "Caching": False} diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 53ac00bee2..33565be6ea 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -23,6 +23,8 @@ class SwaggerEditor(object): _X_APIGW_GATEWAY_RESPONSES = "x-amazon-apigateway-gateway-responses" _X_APIGW_POLICY = "x-amazon-apigateway-policy" _X_ANY_METHOD = "x-amazon-apigateway-any-method" + _X_APIGW_REQUEST_VALIDATORS = "x-amazon-apigateway-request-validators" + _X_APIGW_REQUEST_VALIDATOR = "x-amazon-apigateway-request-validator" _CACHE_KEY_PARAMETERS = "cacheKeyParameters" # https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html _ALL_HTTP_METHODS = ["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "PATCH"] @@ -781,6 +783,46 @@ def _set_method_apikey_handling(self, path, method_name, apikey_required): if security != existing_security: method_definition["security"] = security + def add_request_validator_to_method(self, path, method_name, validate_body=False, validate_parameters=False): + """ + Adds request model body parameter for this path/method. + + :param string path: Path name + :param string method_name: Method name + :param bool validate_body: Add validator parameter on the body + :param bool validate_parameters: Validate request + """ + + normalized_method_name = self._normalize_method_name(method_name) + validator_name = SwaggerEditor.get_validator_name(validate_body, validate_parameters) + + # Creating validator + request_validator_definition = { + validator_name: {"validateRequestBody": validate_body, "validateRequestParameters": validate_parameters} + } + if not self._doc.get(self._X_APIGW_REQUEST_VALIDATORS): + self._doc[self._X_APIGW_REQUEST_VALIDATORS] = {} + + if not self._doc[self._X_APIGW_REQUEST_VALIDATORS].get(validator_name): + # Adding only if the validator hasn't been defined already + self._doc[self._X_APIGW_REQUEST_VALIDATORS].update(request_validator_definition) + + # It is possible that the method could have two definitions in a Fn::If block. + for path_method_name, method in self.get_path(path).items(): + normalized_path_method_name = self._normalize_method_name(path_method_name) + + # Adding it to only given method to the path + if normalized_path_method_name == normalized_method_name: + for method_definition in self.get_method_contents(method): + + # If no integration given, then we don't need to process this definition (could be AWS::NoValue) + if not self.method_definition_has_integration(method_definition): + continue + + set_validator_to_method = {self._X_APIGW_REQUEST_VALIDATOR: validator_name} + # Setting validator to the given method + method_definition.update(set_validator_to_method) + def add_request_model_to_method(self, path, method_name, request_model): """ Adds request model body parameter for this path/method. @@ -1265,6 +1307,26 @@ def get_path_without_trailing_slash(path): # convert greedy paths to such as {greedy+}, {proxy+} to "*" return re.sub(r"{([a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+\+|proxy\+)}", "*", path) + @staticmethod + def get_validator_name(validate_body, validate_parameters): + """ + Get a readable path name to use as validator name + + :param boolean validate_body: Boolean if validate body + :param boolean validate_request: Boolean if validate request + :return string: Normalized validator name + """ + if validate_body and validate_parameters: + return "body-and-params" + + if validate_body and not validate_parameters: + return "body-only" + + if not validate_body and validate_parameters: + return "params-only" + + return "no-validation" + @staticmethod def _validate_list_property_is_resolved(property_list): """ diff --git a/tests/swagger/test_swagger.py b/tests/swagger/test_swagger.py index f237aa1039..7d93d32942 100644 --- a/tests/swagger/test_swagger.py +++ b/tests/swagger/test_swagger.py @@ -831,6 +831,118 @@ def test_must_add_body_parameter_to_method_openapi_required_true(self): self.assertEqual(expected, editor.swagger["paths"]["/foo"]["get"]["requestBody"]) +class TestSwaggerEditor_add_request_validator_to_method(TestCase): + def setUp(self): + + self.original_swagger = { + "swagger": "2.0", + "paths": { + "/foo": { + "get": { + "x-amazon-apigateway-integration": {"test": "must have integration"}, + "parameters": [{"test": "existing parameter"}], + } + } + }, + } + + self.editor = SwaggerEditor(self.original_swagger) + + def test_must_add_validator_parameters_to_method_with_validators_true(self): + + self.editor.add_request_validator_to_method("/foo", "get", True, True) + expected = {"body-and-params": {"validateRequestBody": True, "validateRequestParameters": True}} + + self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"]) + self.assertEqual( + "body-and-params", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"] + ) + + def test_must_add_validator_parameters_to_method_with_validators_false(self): + + self.editor.add_request_validator_to_method("/foo", "get", False, False) + + expected = {"no-validation": {"validateRequestBody": False, "validateRequestParameters": False}} + + self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"]) + self.assertEqual( + "no-validation", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"] + ) + + def test_must_add_validator_parameters_to_method_with_validators_mixing(self): + + self.editor.add_request_validator_to_method("/foo", "get", True, False) + + expected = {"body-only": {"validateRequestBody": True, "validateRequestParameters": False}} + + self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"]) + self.assertEqual( + "body-only", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"] + ) + + def test_must_add_validator_parameters_to_method_and_not_duplicate(self): + self.original_swagger["paths"].update( + { + "/bar": { + "get": { + "x-amazon-apigateway-integration": {"test": "must have integration"}, + "parameters": [{"test": "existing parameter"}], + } + }, + "/foo-bar": { + "get": { + "x-amazon-apigateway-integration": {"test": "must have integration"}, + "parameters": [{"test": "existing parameter"}], + } + }, + } + ) + + editor = SwaggerEditor(self.original_swagger) + + editor.add_request_validator_to_method("/foo", "get", True, True) + editor.add_request_validator_to_method("/bar", "get", True, True) + editor.add_request_validator_to_method("/foo-bar", "get", True, True) + + expected = {"body-and-params": {"validateRequestBody": True, "validateRequestParameters": True}} + + self.assertEqual(expected, editor.swagger["x-amazon-apigateway-request-validators"]) + self.assertEqual( + "body-and-params", editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"] + ) + self.assertEqual( + "body-and-params", editor.swagger["paths"]["/bar"]["get"]["x-amazon-apigateway-request-validator"] + ) + self.assertEqual( + "body-and-params", editor.swagger["paths"]["/foo-bar"]["get"]["x-amazon-apigateway-request-validator"] + ) + + self.assertEqual(1, len(editor.swagger["x-amazon-apigateway-request-validators"].keys())) + + @parameterized.expand( + [ + param(True, False, "body-only"), + param(True, True, "body-and-params"), + param(False, True, "params-only"), + param(False, False, "no-validation"), + ] + ) + def test_must_return_validator_names(self, validate_body, validate_request, normalized_name): + normalized_validator_name_conversion = SwaggerEditor.get_validator_name(validate_body, validate_request) + self.assertEqual(normalized_validator_name_conversion, normalized_name) + + def test_must_add_validator_parameters_to_method_with_validators_false_by_default(self): + + self.editor.add_request_validator_to_method("/foo", "get") + + expected = {"no-validation": {"validateRequestBody": False, "validateRequestParameters": False}} + + self.assertEqual(expected, self.editor.swagger["x-amazon-apigateway-request-validators"]) + self.assertEqual( + "no-validation", self.editor.swagger["paths"]["/foo"]["get"]["x-amazon-apigateway-request-validator"] + ) + + class TestSwaggerEditor_add_auth(TestCase): def setUp(self): diff --git a/tests/translator/input/api_request_model_with_validator.yaml b/tests/translator/input/api_request_model_with_validator.yaml new file mode 100644 index 0000000000..8c0f0629b8 --- /dev/null +++ b/tests/translator/input/api_request_model_with_validator.yaml @@ -0,0 +1,157 @@ +Resources: + HtmlFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: / + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: true + ValidateParameters: true + + HtmlFunctionNoValidation: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /no-validation + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: false + ValidateParameters: false + + HtmlFunctionMixinValidation: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /mixin + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: true + ValidateParameters: false + + HtmlFunctionOnlyBodyDefinition: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /only-body-true + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: true + + HtmlFunctionOnlyRequestDefinition: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /only-request-true + Method: get + RequestModel: + Model: User + Required: true + ValidateParameters: true + + HtmlFunctionOnlyRequestDefinitionFalse: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /only-request-false + Method: get + RequestModel: + Model: User + Required: true + ValidateParameters: false + + HtmlFunctionOnlyBodyDefinitionFalse: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /only-body-false + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: false + + HtmlFunctionNotDefinedValidation: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /not-defined + Method: get + RequestModel: + Model: User + Required: true + + HtmlApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Models: + User: + type: object + properties: + username: + type: string \ No newline at end of file diff --git a/tests/translator/input/api_request_model_with_validator_openapi_3.yaml b/tests/translator/input/api_request_model_with_validator_openapi_3.yaml new file mode 100644 index 0000000000..a7c2878492 --- /dev/null +++ b/tests/translator/input/api_request_model_with_validator_openapi_3.yaml @@ -0,0 +1,32 @@ +Resources: + HtmlFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: / + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: true + ValidateParameters: true + + + HtmlApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + OpenApiVersion: 3.0 + Models: + User: + type: object + properties: + username: + type: string \ No newline at end of file diff --git a/tests/translator/input/error_api_request_model_with_intrinsics_validator.yaml b/tests/translator/input/error_api_request_model_with_intrinsics_validator.yaml new file mode 100644 index 0000000000..0d655e85ea --- /dev/null +++ b/tests/translator/input/error_api_request_model_with_intrinsics_validator.yaml @@ -0,0 +1,88 @@ +Parameters: + ValidateParametersBody: + Type: String + default: true + AllowedValues: [true, false] + ValidateParametersParameters: + Type: String + default: true + AllowedValues: [true, false] + +Conditions: + CreateBodyValidator: + Fn::Equals: + - false + - true + + +Resources: + HtmlFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: / + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: !Ref ValidateParametersBody + ValidateParameters: !Ref ValidateParametersParameters + + HtmlFunctionNoValue: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /novalue + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: AWS::NoValue + + HtmlFunctionWithIfIntrinsics: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: /if/intrinics + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: + Fn::If: + - CreateBodyValidator + - !Ref ValidateParametersBody + - false + + + HtmlApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Models: + User: + type: object + properties: + username: + type: string \ No newline at end of file diff --git a/tests/translator/input/error_api_request_model_with_strings_validator.yaml b/tests/translator/input/error_api_request_model_with_strings_validator.yaml new file mode 100644 index 0000000000..48ae1fdc37 --- /dev/null +++ b/tests/translator/input/error_api_request_model_with_strings_validator.yaml @@ -0,0 +1,38 @@ +Resources: + RequestValidator: + Type: AWS::ApiGateway::RequestValidator + Properties: + Name: "validator" + RestApiId: !Ref HtmlApi + ValidateParametersBody: true + ValidateParametersParameters: true + + HtmlFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + RestApiId: HtmlApi + Path: / + Method: get + RequestModel: + Model: User + Required: true + ValidateBody: "true" + ValidateParameters: "true" + + HtmlApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Models: + User: + type: object + properties: + username: + type: string \ No newline at end of file diff --git a/tests/translator/output/api_request_model_with_validator.json b/tests/translator/output/api_request_model_with_validator.json new file mode 100644 index 0000000000..403404c80f --- /dev/null +++ b/tests/translator/output/api_request_model_with_validator.json @@ -0,0 +1,829 @@ +{ + "Resources": { + "HtmlFunctionOnlyBodyDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionNotDefinedValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNotDefinedValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNotDefinedValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-defined", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymentcf1c484047" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/only-request-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "params-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/mixin": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionMixinValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-request-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/not-defined": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNotDefinedValidation.Arn}/invocations" + } + }, + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/no-validation": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNoValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-request-validators": { + "params-only": { + "validateRequestParameters": true, + "validateRequestBody": false + }, + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + }, + "no-validation": { + "validateRequestParameters": false, + "validateRequestBody": false + }, + "body-only": { + "validateRequestParameters": false, + "validateRequestBody": true + } + }, + "definitions": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + } + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNoValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/no-validation", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyRequestDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyRequestDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNoValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionNoValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionMixinValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionMixinValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/mixin", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionMixinValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlApiDeploymentcf1c484047": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: cf1c4840474016f8258f9b83ddb9292d01a2d7ad", + "StageName": "Stage" + } + }, + "HtmlFunctionOnlyBodyDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyBodyDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_request_model_with_validator_openapi_3.json b/tests/translator/output/api_request_model_with_validator_openapi_3.json new file mode 100644 index 0000000000..2e52f132f5 --- /dev/null +++ b/tests/translator/output/api_request_model_with_validator_openapi_3.json @@ -0,0 +1,154 @@ +{ + "Resources": { + "HtmlApiDeploymentafd82ad9d8": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: afd82ad9d87f63f99528abd3b6668b168b5e49d7" + } + }, + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymentafd82ad9d8" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + }, + "required": true + }, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "schemas": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + }, + "x-amazon-apigateway-request-validators": { + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + } + } + } + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} diff --git a/tests/translator/output/aws-cn/api_request_model_with_validator.json b/tests/translator/output/aws-cn/api_request_model_with_validator.json new file mode 100644 index 0000000000..c392a23538 --- /dev/null +++ b/tests/translator/output/aws-cn/api_request_model_with_validator.json @@ -0,0 +1,837 @@ +{ + "Resources": { + "HtmlFunctionOnlyBodyDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionNotDefinedValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNotDefinedValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNotDefinedValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-defined", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymentdf07d632f4" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/only-request-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "params-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/mixin": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionMixinValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-request-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/not-defined": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNotDefinedValidation.Arn}/invocations" + } + }, + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/no-validation": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNoValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-request-validators": { + "params-only": { + "validateRequestParameters": true, + "validateRequestBody": false + }, + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + }, + "no-validation": { + "validateRequestParameters": false, + "validateRequestBody": false + }, + "body-only": { + "validateRequestParameters": false, + "validateRequestBody": true + } + }, + "definitions": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNoValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/no-validation", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyRequestDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApiDeploymentdf07d632f4": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: df07d632f424a561531bf71d3c41ea245b01d80e", + "StageName": "Stage" + } + }, + "HtmlFunctionOnlyRequestDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNoValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionNoValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionMixinValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionMixinValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/mixin", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionMixinValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyBodyDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_request_model_with_validator_openapi_3.json b/tests/translator/output/aws-cn/api_request_model_with_validator_openapi_3.json new file mode 100644 index 0000000000..57bf8cf6eb --- /dev/null +++ b/tests/translator/output/aws-cn/api_request_model_with_validator_openapi_3.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "HtmlApiDeploymentfa918fe8b6": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: fa918fe8b68f65e882542b900cc422005ea1a7f6" + } + }, + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymentfa918fe8b6" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + }, + "required": true + }, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "schemas": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + }, + "x-amazon-apigateway-request-validators": { + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_request_model_with_validator.json b/tests/translator/output/aws-us-gov/api_request_model_with_validator.json new file mode 100644 index 0000000000..e7a1e3218f --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_request_model_with_validator.json @@ -0,0 +1,837 @@ +{ + "Resources": { + "HtmlFunctionOnlyBodyDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionNotDefinedValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNotDefinedValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNotDefinedValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNotDefinedValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-defined", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymentf186539e09" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/only-request-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "params-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/mixin": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionMixinValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-request-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyRequestDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-true": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinition.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-only", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/not-defined": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNotDefinedValidation.Arn}/invocations" + } + }, + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/no-validation": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionNoValidation.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + }, + "/only-body-false": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunctionOnlyBodyDefinitionFalse.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "no-validation", + "responses": {}, + "parameters": [ + { + "required": true, + "in": "body", + "name": "user", + "schema": { + "$ref": "#/definitions/user" + } + } + ] + } + } + }, + "swagger": "2.0", + "x-amazon-apigateway-request-validators": { + "params-only": { + "validateRequestParameters": true, + "validateRequestBody": false + }, + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + }, + "no-validation": { + "validateRequestParameters": false, + "validateRequestBody": false + }, + "body-only": { + "validateRequestParameters": false, + "validateRequestBody": true + } + }, + "definitions": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalse": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionFalseRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionNoValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/no-validation", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyRequestDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApiDeploymentf186539e09": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: f186539e0968ebab9c817becf503f8f5f6d029fd", + "StageName": "Stage" + } + }, + "HtmlFunctionOnlyRequestDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyRequestDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionNoValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionNoValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinition": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionOnlyBodyDefinitionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyRequestDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyRequestDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-request-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionNoValidationRole": { + "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" + } + ] + } + }, + "HtmlFunctionMixinValidationGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionMixinValidation" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/mixin", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionMixinValidation": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionMixinValidationRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinitionRole": { + "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" + } + ] + } + }, + "HtmlFunctionOnlyBodyDefinitionFalseGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinitionFalse" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-false", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlFunctionOnlyBodyDefinitionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunctionOnlyBodyDefinition" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/only-body-true", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_request_model_with_validator_openapi_3.json b/tests/translator/output/aws-us-gov/api_request_model_with_validator_openapi_3.json new file mode 100644 index 0000000000..83fd018130 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_request_model_with_validator_openapi_3.json @@ -0,0 +1,162 @@ +{ + "Resources": { + "HtmlFunctionRole": { + "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" + } + ] + } + }, + "HtmlApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "HtmlApiDeploymente5c2c7da3d" + }, + "RestApiId": { + "Ref": "HtmlApi" + }, + "StageName": "Prod" + } + }, + "HtmlFunctionGetHtmlPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "HtmlFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": "HtmlApi" + } + ] + } + } + }, + "HtmlApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + }, + "required": true + }, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HtmlFunction.Arn}/invocations" + } + }, + "x-amazon-apigateway-request-validator": "body-and-params", + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "schemas": { + "user": { + "type": "object", + "properties": { + "username": { + "type": "string" + } + } + } + } + }, + "x-amazon-apigateway-request-validators": { + "body-and-params": { + "validateRequestParameters": true, + "validateRequestBody": true + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "HtmlApiDeploymente5c2c7da3d": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "HtmlApi" + }, + "Description": "RestApi deployment id: e5c2c7da3d6e36d656af91f88bf7d1e773111a32" + } + }, + "HtmlFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HtmlFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_api_request_model_with_intrinsics_validator.json b/tests/translator/output/error_api_request_model_with_intrinsics_validator.json new file mode 100644 index 0000000000..c51ae4c457 --- /dev/null +++ b/tests/translator/output/error_api_request_model_with_intrinsics_validator.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 3. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set Validator to RequestModel [User] on API method [get] for path [/] ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported. Resource with id [HtmlFunctionNoValue] is invalid. Event with id [GetHtml] is invalid. Unable to set Validator to RequestModel [User] on API method [get] for path [/novalue] ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported. Resource with id [HtmlFunctionWithIfIntrinsics] is invalid. Event with id [GetHtml] is invalid. Unable to set Validator to RequestModel [User] on API method [get] for path [/if/intrinics] ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported." +} diff --git a/tests/translator/output/error_api_request_model_with_strings_validator.json b/tests/translator/output/error_api_request_model_with_strings_validator.json new file mode 100644 index 0000000000..92b7652590 --- /dev/null +++ b/tests/translator/output/error_api_request_model_with_strings_validator.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [HtmlFunction] is invalid. Event with id [GetHtml] is invalid. Unable to set Validator to RequestModel [User] on API method [get] for path [/] ValidateBody and ValidateParameters must be a boolean type, strings or intrinsics are not supported." +} diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index ba244dd0b5..e22bff999d 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -320,6 +320,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "api_with_canary_setting", "api_with_xray_tracing", "api_request_model", + "api_request_model_with_validator", "api_with_stage_tags", "api_with_mode", "s3", @@ -472,6 +473,7 @@ def test_transform_success(self, testcase, partition_with_region): "api_with_auth_all_minimum_openapi", "api_with_swagger_and_openapi_with_auth", "api_with_openapi_definition_body_no_flag", + "api_request_model_with_validator_openapi_3", "api_request_model_openapi_3", "api_with_apikey_required_openapi_3", "api_with_basic_custom_domain", @@ -668,6 +670,8 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_api_gateway_responses_nonnumeric_status_code", "error_api_gateway_responses_unknown_responseparameter", "error_api_gateway_responses_unknown_responseparameter_property", + "error_api_request_model_with_intrinsics_validator", + "error_api_request_model_with_strings_validator", "error_api_invalid_auth", "error_api_invalid_path", "error_api_invalid_definitionuri", From 4d432d71d87e6192af7b9a997820e2a4187a2b34 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 30 Jun 2021 15:21:46 -0700 Subject: [PATCH 14/28] fix: fail with invalid resource, when RetentionPolicy has unresolved intrinsic value (#2074) * fix: fail with invalid resource, when RetentionPolicy has unresolved intrinsic value * make black * remove extra formatting * add test case for !ref --- samtranslator/model/sam_resources.py | 12 ++++++- tests/translator/input/basic_layer.yaml | 19 ++++++++++- .../input/error_layer_invalid_properties.yaml | 11 ++++++- .../translator/output/aws-cn/basic_layer.json | 30 ++++++++++++++++- .../output/aws-us-gov/basic_layer.json | 32 +++++++++++++++++-- tests/translator/output/basic_layer.json | 30 ++++++++++++++++- .../error_layer_invalid_properties.json | 2 +- 7 files changed, 128 insertions(+), 8 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index e8ddd291e5..473f54d72e 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -37,6 +37,7 @@ from samtranslator.translator import logical_id_generator from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.model.intrinsics import ( + is_intrinsic, is_intrinsic_if, is_intrinsic_no_value, ref, @@ -1224,6 +1225,15 @@ def _get_retention_policy_value(self): :return: value for the DeletionPolicy attribute. """ + if is_intrinsic(self.RetentionPolicy): + # RetentionPolicy attribute of AWS::Serverless::LayerVersion does set the DeletionPolicy + # attribute. And DeletionPolicy attribute does not support intrinsic values. + raise InvalidResourceException( + self.logical_id, + "'RetentionPolicy' does not accept intrinsic functions, " + "please use one of the following options: {}".format([self.RETAIN, self.DELETE]), + ) + if self.RetentionPolicy is None: return None elif self.RetentionPolicy.lower() == self.RETAIN.lower(): @@ -1233,7 +1243,7 @@ def _get_retention_policy_value(self): elif self.RetentionPolicy.lower() not in self.retention_policy_options: raise InvalidResourceException( self.logical_id, - "'{}' must be one of the following options: {}.".format("RetentionPolicy", [self.RETAIN, self.DELETE]), + "'RetentionPolicy' must be one of the following options: {}.".format([self.RETAIN, self.DELETE]), ) diff --git a/tests/translator/input/basic_layer.yaml b/tests/translator/input/basic_layer.yaml index 170af09b1a..6bed1762ae 100644 --- a/tests/translator/input/basic_layer.yaml +++ b/tests/translator/input/basic_layer.yaml @@ -4,6 +4,11 @@ Conditions: - beta - beta +Parameters: + DeletePolicy: + Default: Retain + Type: String + Resources: MinimalLayer: Type: 'AWS::Serverless::LayerVersion' @@ -35,4 +40,16 @@ Resources: Type: 'AWS::Serverless::LayerVersion' Condition: TestCondition Properties: - ContentUri: s3://sam-demo-bucket/layer.zip + ContentUri: s3://sam-demo-bucket/layer.zip + + LayerWithCaseInsensitiveRetentionPolicy: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + RetentionPolicy: DeleTe + + LayerWithRetentionPolicyParam: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + RetentionPolicy: !Ref DeletePolicy diff --git a/tests/translator/input/error_layer_invalid_properties.yaml b/tests/translator/input/error_layer_invalid_properties.yaml index 606530d6ad..c6b9c531d2 100644 --- a/tests/translator/input/error_layer_invalid_properties.yaml +++ b/tests/translator/input/error_layer_invalid_properties.yaml @@ -6,6 +6,9 @@ Parameters: Default: - Retain Type: List + DeletePolicyIf: + Default: False + Type: Boolean Resources: LayerWithRuntimesString: @@ -43,4 +46,10 @@ Resources: Type: 'AWS::Serverless::LayerVersion' Properties: ContentUri: s3://sam-demo-bucket/layer.zip - RetentionPolicy: !Ref DeleteList \ No newline at end of file + RetentionPolicy: !Ref DeleteList + + LayerWithRetentionPolicyAsIntrinsic: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + RetentionPolicy: !If [DeletePolicyIf, Retain, Delete] \ No newline at end of file diff --git a/tests/translator/output/aws-cn/basic_layer.json b/tests/translator/output/aws-cn/basic_layer.json index d373bd19c9..33816f0ad7 100644 --- a/tests/translator/output/aws-cn/basic_layer.json +++ b/tests/translator/output/aws-cn/basic_layer.json @@ -6,7 +6,13 @@ "beta" ] } - }, + }, + "Parameters": { + "DeletePolicy": { + "Default": "Retain", + "Type": "String" + } + }, "Resources": { "LayerWithCondition7c655e10ea": { "DeletionPolicy": "Retain", @@ -59,6 +65,28 @@ }, "LayerName": "LayerWithContentUriObject" } + }, + "LayerWithCaseInsensitiveRetentionPolicyead52a491d": { + "DeletionPolicy": "Delete", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithCaseInsensitiveRetentionPolicy" + } + }, + "LayerWithRetentionPolicyParamcc64815342": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithRetentionPolicyParam" + } } } } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/basic_layer.json b/tests/translator/output/aws-us-gov/basic_layer.json index d373bd19c9..707f6a57c3 100644 --- a/tests/translator/output/aws-us-gov/basic_layer.json +++ b/tests/translator/output/aws-us-gov/basic_layer.json @@ -6,7 +6,13 @@ "beta" ] } - }, + }, + "Parameters": { + "DeletePolicy": { + "Default": "Retain", + "Type": "String" + } + }, "Resources": { "LayerWithCondition7c655e10ea": { "DeletionPolicy": "Retain", @@ -47,7 +53,7 @@ "python2.7" ] } - }, + }, "LayerWithContentUriObjectbdbf1b82ac": { "DeletionPolicy": "Delete", "Type": "AWS::Lambda::LayerVersion", @@ -59,6 +65,28 @@ }, "LayerName": "LayerWithContentUriObject" } + }, + "LayerWithCaseInsensitiveRetentionPolicyead52a491d": { + "DeletionPolicy": "Delete", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithCaseInsensitiveRetentionPolicy" + } + }, + "LayerWithRetentionPolicyParamcc64815342": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithRetentionPolicyParam" + } } } } \ No newline at end of file diff --git a/tests/translator/output/basic_layer.json b/tests/translator/output/basic_layer.json index d373bd19c9..33816f0ad7 100644 --- a/tests/translator/output/basic_layer.json +++ b/tests/translator/output/basic_layer.json @@ -6,7 +6,13 @@ "beta" ] } - }, + }, + "Parameters": { + "DeletePolicy": { + "Default": "Retain", + "Type": "String" + } + }, "Resources": { "LayerWithCondition7c655e10ea": { "DeletionPolicy": "Retain", @@ -59,6 +65,28 @@ }, "LayerName": "LayerWithContentUriObject" } + }, + "LayerWithCaseInsensitiveRetentionPolicyead52a491d": { + "DeletionPolicy": "Delete", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithCaseInsensitiveRetentionPolicy" + } + }, + "LayerWithRetentionPolicyParamcc64815342": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithRetentionPolicyParam" + } } } } \ No newline at end of file diff --git a/tests/translator/output/error_layer_invalid_properties.json b/tests/translator/output/error_layer_invalid_properties.json index 696ca48b0d..f4eaa83f8c 100644 --- a/tests/translator/output/error_layer_invalid_properties.json +++ b/tests/translator/output/error_layer_invalid_properties.json @@ -4,5 +4,5 @@ "errorMessage": "Resource with id [LayerWithLicenseInfoList] is invalid. Type of property 'LicenseInfo' is invalid. Resource with id [LayerWithNoContentUri] is invalid. Missing required property 'ContentUri'. Resource with id [LayerWithRetentionPolicy] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRetentionPolicyListParam] is invalid. Could not resolve parameter for 'RetentionPolicy' or parameter is not a String. Resource with id [LayerWithRetentionPolicyParam] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRuntimesString] is invalid. Type of property 'CompatibleRuntimes' is invalid." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 6. Resource with id [LayerWithLicenseInfoList] is invalid. Type of property 'LicenseInfo' is invalid. Resource with id [LayerWithNoContentUri] is invalid. Missing required property 'ContentUri'. Resource with id [LayerWithRetentionPolicy] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRetentionPolicyListParam] is invalid. Could not resolve parameter for 'RetentionPolicy' or parameter is not a String. Resource with id [LayerWithRetentionPolicyParam] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRuntimesString] is invalid. Type of property 'CompatibleRuntimes' is invalid." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 7. Resource with id [LayerWithLicenseInfoList] is invalid. Type of property 'LicenseInfo' is invalid. Resource with id [LayerWithNoContentUri] is invalid. Missing required property 'ContentUri'. Resource with id [LayerWithRetentionPolicy] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRetentionPolicyAsIntrinsic] is invalid. 'RetentionPolicy' does not accept intrinsic functions, please use one of the following options: ['Retain', 'Delete'] Resource with id [LayerWithRetentionPolicyListParam] is invalid. Could not resolve parameter for 'RetentionPolicy' or parameter is not a String. Resource with id [LayerWithRetentionPolicyParam] is invalid. 'RetentionPolicy' must be one of the following options: ['Retain', 'Delete']. Resource with id [LayerWithRuntimesString] is invalid. Type of property 'CompatibleRuntimes' is invalid." } \ No newline at end of file From c4de6a01e73bf115f746b8c83bd6a82a25a54374 Mon Sep 17 00:00:00 2001 From: mingkun2020 <68391979+mingkun2020@users.noreply.github.com> Date: Fri, 9 Jul 2021 10:43:58 -0700 Subject: [PATCH 15/28] test: Migration of combination integration tests (#1970) --- integration/combination/__init__.py | 0 integration/combination/test_api_settings.py | 179 +++++++ .../combination/test_api_with_authorizers.py | 479 ++++++++++++++++++ integration/combination/test_api_with_cors.py | 99 ++++ .../test_api_with_gateway_responses.py | 35 ++ .../test_api_with_resource_policies.py | 196 +++++++ .../combination/test_api_with_usage_plan.py | 22 + integration/combination/test_depends_on.py | 8 + .../combination/test_function_with_alias.py | 170 +++++++ .../test_function_with_all_event_types.py | 118 +++++ .../combination/test_function_with_api.py | 31 ++ .../test_function_with_application.py | 17 + .../test_function_with_cloudwatch_log.py | 19 + ..._function_with_cwe_dlq_and_retry_policy.py | 24 + .../test_function_with_cwe_dlq_generated.py | 66 +++ ...est_function_with_deployment_preference.py | 157 ++++++ .../test_function_with_dynamoDB.py | 24 + .../test_function_with_file_system_config.py | 6 + .../test_function_with_http_api.py | 12 + ...nction_with_implicit_api_and_conditions.py | 6 + .../test_function_with_implicit_http_api.py | 12 + .../combination/test_function_with_kinesis.py | 24 + .../combination/test_function_with_layers.py | 17 + .../combination/test_function_with_mq.py | 38 ++ .../combination/test_function_with_msk.py | 34 ++ .../test_function_with_s3_bucket.py | 18 + .../test_function_with_schedule.py | 20 + ...tion_with_schedule_dlq_and_retry_policy.py | 31 ++ ...st_function_with_schedule_dlq_generated.py | 84 +++ .../test_function_with_signing_profile.py | 6 + .../combination/test_function_with_sns.py | 28 + .../combination/test_function_with_sqs.py | 24 + .../test_function_with_user_pool_event.py | 11 + .../combination/test_http_api_with_auth.py | 83 +++ .../combination/test_http_api_with_cors.py | 43 ++ ...p_api_with_disable_execute_api_endpoint.py | 18 + .../test_intrinsic_function_support.py | 60 +++ .../combination/test_resource_references.py | 49 ++ .../test_state_machine_with_api.py | 88 ++++ .../test_state_machine_with_cwe.py | 43 ++ ...e_machine_with_cwe_dlq_and_retry_policy.py | 24 + ...st_state_machine_with_cwe_dlq_generated.py | 107 ++++ ...est_state_machine_with_policy_templates.py | 47 ++ .../test_state_machine_with_schedule.py | 45 ++ ...hine_with_schedule_dlq_and_retry_policy.py | 58 +++ ...ate_machine_with_schedule_dlq_generated.py | 79 +++ integration/helpers/base_test.py | 125 ++++- integration/helpers/client_provider.py | 149 +++++- integration/helpers/common_api.py | 21 + integration/helpers/deployer/deployer.py | 2 +- integration/helpers/deployer/utils/retry.py | 40 ++ .../deployer/utils/{time.py => time_util.py} | 0 integration/helpers/exception.py | 7 + integration/helpers/file_resources.py | 5 + integration/helpers/resource.py | 20 + integration/resources/code/AWS_logo_RGB.png | Bin 0 -> 4217 bytes .../resources/code/MTLSCert-Updated.pem | 30 ++ integration/resources/code/MTLSCert.pem | 30 ++ integration/resources/code/binary-media.zip | Bin 0 -> 5481 bytes integration/resources/code/code2.zip | Bin 0 -> 231 bytes .../combination/all_policy_templates.json | 8 + ...h_authorizers_invokefunction_set_none.json | 15 + .../combination/api_with_authorizers_max.json | 21 + .../api_with_authorizers_max_openapi.json | 25 + .../combination/api_with_authorizers_min.json | 18 + .../api_with_binary_media_types.json | 5 + ...nary_media_types_with_definition_body.json | 5 + ...ia_types_with_definition_body_openapi.json | 8 + .../expected/combination/api_with_cors.json | 9 + .../api_with_cors_only_headers.json | 9 + .../api_with_cors_only_max_age.json | 9 + .../api_with_cors_only_methods.json | 9 + .../combination/api_with_cors_openapi.json | 9 + .../combination/api_with_cors_shorthand.json | 9 + .../api_with_endpoint_configuration.json | 5 + .../api_with_endpoint_configuration_dict.json | 5 + .../api_with_gateway_responses.json | 8 + .../combination/api_with_method_settings.json | 5 + .../combination/api_with_request_models.json | 8 + .../api_with_request_models_openapi.json | 8 + .../api_with_request_parameters_openapi.json | 9 + .../api_with_resource_policies.json | 9 + ...pi_with_resource_policies_aws_account.json | 8 + .../combination/api_with_resource_refs.json | 5 + .../combination/api_with_usage_plan.json | 20 + .../expected/combination/depends_on.json | 5 + .../combination/function_with_alias.json | 6 + ...function_with_alias_and_event_sources.json | 27 + .../function_with_alias_globals.json | 10 + .../function_with_alias_intrinsics.json | 6 + .../function_with_all_event_types.json | 31 ++ ..._with_all_event_types_condition_false.json | 6 + .../combination/function_with_api.json | 9 + .../function_with_application.json | 5 + .../function_with_cloudwatch_log.json | 7 + .../function_with_custom_code_deploy.json | 9 + ...unction_with_cwe_dlq_and_retry_policy.json | 7 + .../function_with_cwe_dlq_generated.json | 8 + ...tion_with_deployment_alarms_and_hooks.json | 16 + .../function_with_deployment_basic.json | 9 + ...eployment_default_role_managed_policy.json | 9 + .../function_with_deployment_disabled.json | 7 + .../function_with_deployment_globals.json | 9 + .../combination/function_with_dynamodb.json | 6 + .../function_with_file_system_config.json | 10 + .../combination/function_with_http_api.json | 7 + ...tion_with_implicit_api_and_conditions.json | 14 + .../function_with_implicit_http_api.json | 7 + .../combination/function_with_kinesis.json | 6 + .../combination/function_with_layer.json | 5 + .../combination/function_with_mq.json | 15 + .../function_with_mq_using_autogen_role.json | 15 + .../combination/function_with_msk.json | 9 + ...unction_with_msk_using_managed_policy.json | 9 + .../function_with_policy_templates.json | 5 + .../function_with_resource_refs.json | 8 + .../combination/function_with_s3.json | 6 + .../combination/function_with_schedule.json | 6 + ...on_with_schedule_dlq_and_retry_policy.json | 7 + .../function_with_schedule_dlq_generated.json | 8 + .../function_with_signing_profile.json | 6 + .../combination/function_with_sns.json | 11 + .../combination/function_with_sqs.json | 6 + .../function_with_userpool_event.json | 6 + .../combination/http_api_with_auth.json | 11 + .../combination/http_api_with_cors.json | 7 + ...http_api_with_custom_domains_regional.json | 12 + ...th_disable_execute_api_endpoint_false.json | 8 + ...ith_disable_execute_api_endpoint_true.json | 8 + .../implicit_api_with_settings.json | 8 + .../intrinsics_code_definition_uri.json | 7 + .../intrinsics_serverless_api.json | 9 + .../intrinsics_serverless_function.json | 7 + .../combination/state_machine_with_api.json | 12 + .../combination/state_machine_with_cwe.json | 6 + .../state_machine_with_cwe_dlq_generated.json | 8 + ...ne_with_cwe_with_dlq_and_retry_policy.json | 7 + .../state_machine_with_policy_templates.json | 7 + .../state_machine_with_schedule.json | 6 + ...ne_with_schedule_dlq_and_retry_policy.json | 7 + ...e_machine_with_schedule_dlq_generated.json | 8 + .../combination/all_policy_templates.yaml | 209 ++++++++ ...h_authorizers_invokefunction_set_none.yaml | 76 +++ .../combination/api_with_authorizers_max.yaml | 196 +++++++ .../api_with_authorizers_max_openapi.yaml | 245 +++++++++ .../combination/api_with_authorizers_min.yaml | 130 +++++ .../api_with_binary_media_types.yaml | 22 + ...nary_media_types_with_definition_body.yaml | 46 ++ ...ia_types_with_definition_body_openapi.yaml | 77 +++ .../templates/combination/api_with_cors.yaml | 46 ++ .../api_with_cors_only_headers.yaml | 48 ++ .../api_with_cors_only_max_age.yaml | 48 ++ .../api_with_cors_only_methods.yaml | 48 ++ .../combination/api_with_cors_openapi.yaml | 46 ++ .../combination/api_with_cors_shorthand.yaml | 47 ++ .../api_with_endpoint_configuration.yaml | 21 + .../api_with_endpoint_configuration_dict.yaml | 17 + .../api_with_gateway_responses.yaml | 39 ++ .../combination/api_with_method_settings.yaml | 13 + .../combination/api_with_request_models.yaml | 41 ++ .../api_with_request_models_openapi.yaml | 42 ++ .../api_with_request_parameters_openapi.yaml | 47 ++ .../api_with_resource_policies.yaml | 62 +++ ...pi_with_resource_policies_aws_account.yaml | 33 ++ .../combination/api_with_resource_refs.yaml | 22 + .../combination/api_with_usage_plan.yaml | 155 ++++++ .../templates/combination/depends_on.yaml | 51 ++ .../combination/function_with_alias.yaml | 8 + ...function_with_alias_and_event_sources.yaml | 106 ++++ .../function_with_alias_globals.yaml | 21 + .../function_with_alias_intrinsics.yaml | 29 ++ .../function_with_all_event_types.yaml | 157 ++++++ ..._with_all_event_types_condition_false.yaml | 131 +++++ .../combination/function_with_api.yaml | 33 ++ .../function_with_application.yaml | 33 ++ .../function_with_cloudwatch_log.yaml | 20 + .../function_with_custom_code_deploy.yaml | 45 ++ ...unction_with_cwe_dlq_and_retry_policy.yaml | 46 ++ .../function_with_cwe_dlq_generated.yaml | 46 ++ ...tion_with_deployment_alarms_and_hooks.yaml | 128 +++++ .../function_with_deployment_basic.yaml | 45 ++ ...eployment_default_role_managed_policy.yaml | 10 + .../function_with_deployment_disabled.yaml | 59 +++ .../function_with_deployment_globals.yaml | 50 ++ .../combination/function_with_dynamodb.yaml | 42 ++ .../function_with_file_system_config.yaml | 71 +++ .../combination/function_with_http_api.yaml | 37 ++ ...tion_with_implicit_api_and_conditions.yaml | 225 ++++++++ .../function_with_implicit_http_api.yaml | 20 + .../combination/function_with_kinesis.yaml | 27 + .../combination/function_with_layer.yaml | 39 ++ .../combination/function_with_mq.yaml | 188 +++++++ .../function_with_mq_using_autogen_role.yaml | 146 ++++++ .../combination/function_with_msk.yaml | 109 ++++ ...unction_with_msk_using_managed_policy.yaml | 72 +++ .../function_with_policy_templates.yaml | 41 ++ .../function_with_resource_refs.yaml | 44 ++ .../combination/function_with_s3.yaml | 18 + .../combination/function_with_schedule.yaml | 37 ++ ...on_with_schedule_dlq_and_retry_policy.yaml | 38 ++ .../function_with_schedule_dlq_generated.yaml | 40 ++ .../function_with_signing_profile.yaml | 29 ++ .../combination/function_with_sns.yaml | 28 + .../combination/function_with_sqs.yaml | 17 + .../function_with_userpool_event.yaml | 55 ++ .../combination/http_api_with_auth.yaml | 81 +++ .../http_api_with_auth_updated.yaml | 53 ++ .../combination/http_api_with_cors.yaml | 45 ++ ...http_api_with_custom_domains_regional.yaml | 59 +++ ...th_disable_execute_api_endpoint_false.yaml | 38 ++ ...ith_disable_execute_api_endpoint_true.yaml | 39 ++ .../implicit_api_with_settings.yaml | 29 ++ .../intrinsics_code_definition_uri.yaml | 35 ++ .../intrinsics_serverless_api.yaml | 118 +++++ .../intrinsics_serverless_function.yaml | 127 +++++ .../combination/state_machine_with_api.yaml | 74 +++ .../combination/state_machine_with_cwe.yaml | 45 ++ .../state_machine_with_cwe_dlq_generated.yaml | 59 +++ ...ne_with_cwe_with_dlq_and_retry_policy.yaml | 61 +++ .../state_machine_with_policy_templates.yaml | 36 ++ .../state_machine_with_schedule.yaml | 45 ++ ...ne_with_schedule_dlq_and_retry_policy.yaml | 61 +++ ...e_machine_with_schedule_dlq_generated.yaml | 58 +++ integration/single/test_basic_api.py | 12 +- integration/single/test_basic_application.py | 6 +- integration/single/test_basic_function.py | 44 +- integration/single/test_basic_http_api.py | 2 +- .../single/test_basic_layer_version.py | 4 +- .../single/test_basic_state_machine.py | 4 +- requirements/dev.txt | 2 + 230 files changed, 9110 insertions(+), 77 deletions(-) create mode 100644 integration/combination/__init__.py create mode 100644 integration/combination/test_api_settings.py create mode 100644 integration/combination/test_api_with_authorizers.py create mode 100644 integration/combination/test_api_with_cors.py create mode 100644 integration/combination/test_api_with_gateway_responses.py create mode 100644 integration/combination/test_api_with_resource_policies.py create mode 100644 integration/combination/test_api_with_usage_plan.py create mode 100644 integration/combination/test_depends_on.py create mode 100644 integration/combination/test_function_with_alias.py create mode 100644 integration/combination/test_function_with_all_event_types.py create mode 100644 integration/combination/test_function_with_api.py create mode 100644 integration/combination/test_function_with_application.py create mode 100644 integration/combination/test_function_with_cloudwatch_log.py create mode 100644 integration/combination/test_function_with_cwe_dlq_and_retry_policy.py create mode 100644 integration/combination/test_function_with_cwe_dlq_generated.py create mode 100644 integration/combination/test_function_with_deployment_preference.py create mode 100644 integration/combination/test_function_with_dynamoDB.py create mode 100644 integration/combination/test_function_with_file_system_config.py create mode 100644 integration/combination/test_function_with_http_api.py create mode 100644 integration/combination/test_function_with_implicit_api_and_conditions.py create mode 100644 integration/combination/test_function_with_implicit_http_api.py create mode 100644 integration/combination/test_function_with_kinesis.py create mode 100644 integration/combination/test_function_with_layers.py create mode 100644 integration/combination/test_function_with_mq.py create mode 100644 integration/combination/test_function_with_msk.py create mode 100644 integration/combination/test_function_with_s3_bucket.py create mode 100644 integration/combination/test_function_with_schedule.py create mode 100644 integration/combination/test_function_with_schedule_dlq_and_retry_policy.py create mode 100644 integration/combination/test_function_with_schedule_dlq_generated.py create mode 100644 integration/combination/test_function_with_signing_profile.py create mode 100644 integration/combination/test_function_with_sns.py create mode 100644 integration/combination/test_function_with_sqs.py create mode 100644 integration/combination/test_function_with_user_pool_event.py create mode 100644 integration/combination/test_http_api_with_auth.py create mode 100644 integration/combination/test_http_api_with_cors.py create mode 100644 integration/combination/test_http_api_with_disable_execute_api_endpoint.py create mode 100644 integration/combination/test_intrinsic_function_support.py create mode 100644 integration/combination/test_resource_references.py create mode 100644 integration/combination/test_state_machine_with_api.py create mode 100644 integration/combination/test_state_machine_with_cwe.py create mode 100644 integration/combination/test_state_machine_with_cwe_dlq_and_retry_policy.py create mode 100644 integration/combination/test_state_machine_with_cwe_dlq_generated.py create mode 100644 integration/combination/test_state_machine_with_policy_templates.py create mode 100644 integration/combination/test_state_machine_with_schedule.py create mode 100644 integration/combination/test_state_machine_with_schedule_dlq_and_retry_policy.py create mode 100644 integration/combination/test_state_machine_with_schedule_dlq_generated.py create mode 100644 integration/helpers/common_api.py create mode 100644 integration/helpers/deployer/utils/retry.py rename integration/helpers/deployer/utils/{time.py => time_util.py} (100%) create mode 100644 integration/helpers/exception.py create mode 100644 integration/resources/code/AWS_logo_RGB.png create mode 100644 integration/resources/code/MTLSCert-Updated.pem create mode 100644 integration/resources/code/MTLSCert.pem create mode 100644 integration/resources/code/binary-media.zip create mode 100644 integration/resources/code/code2.zip create mode 100644 integration/resources/expected/combination/all_policy_templates.json create mode 100644 integration/resources/expected/combination/api_with_authorizers_invokefunction_set_none.json create mode 100644 integration/resources/expected/combination/api_with_authorizers_max.json create mode 100644 integration/resources/expected/combination/api_with_authorizers_max_openapi.json create mode 100644 integration/resources/expected/combination/api_with_authorizers_min.json create mode 100644 integration/resources/expected/combination/api_with_binary_media_types.json create mode 100644 integration/resources/expected/combination/api_with_binary_media_types_with_definition_body.json create mode 100644 integration/resources/expected/combination/api_with_binary_media_types_with_definition_body_openapi.json create mode 100644 integration/resources/expected/combination/api_with_cors.json create mode 100644 integration/resources/expected/combination/api_with_cors_only_headers.json create mode 100644 integration/resources/expected/combination/api_with_cors_only_max_age.json create mode 100644 integration/resources/expected/combination/api_with_cors_only_methods.json create mode 100644 integration/resources/expected/combination/api_with_cors_openapi.json create mode 100644 integration/resources/expected/combination/api_with_cors_shorthand.json create mode 100644 integration/resources/expected/combination/api_with_endpoint_configuration.json create mode 100644 integration/resources/expected/combination/api_with_endpoint_configuration_dict.json create mode 100644 integration/resources/expected/combination/api_with_gateway_responses.json create mode 100644 integration/resources/expected/combination/api_with_method_settings.json create mode 100644 integration/resources/expected/combination/api_with_request_models.json create mode 100644 integration/resources/expected/combination/api_with_request_models_openapi.json create mode 100644 integration/resources/expected/combination/api_with_request_parameters_openapi.json create mode 100644 integration/resources/expected/combination/api_with_resource_policies.json create mode 100644 integration/resources/expected/combination/api_with_resource_policies_aws_account.json create mode 100644 integration/resources/expected/combination/api_with_resource_refs.json create mode 100644 integration/resources/expected/combination/api_with_usage_plan.json create mode 100644 integration/resources/expected/combination/depends_on.json create mode 100644 integration/resources/expected/combination/function_with_alias.json create mode 100644 integration/resources/expected/combination/function_with_alias_and_event_sources.json create mode 100644 integration/resources/expected/combination/function_with_alias_globals.json create mode 100644 integration/resources/expected/combination/function_with_alias_intrinsics.json create mode 100644 integration/resources/expected/combination/function_with_all_event_types.json create mode 100644 integration/resources/expected/combination/function_with_all_event_types_condition_false.json create mode 100644 integration/resources/expected/combination/function_with_api.json create mode 100644 integration/resources/expected/combination/function_with_application.json create mode 100644 integration/resources/expected/combination/function_with_cloudwatch_log.json create mode 100644 integration/resources/expected/combination/function_with_custom_code_deploy.json create mode 100644 integration/resources/expected/combination/function_with_cwe_dlq_and_retry_policy.json create mode 100644 integration/resources/expected/combination/function_with_cwe_dlq_generated.json create mode 100644 integration/resources/expected/combination/function_with_deployment_alarms_and_hooks.json create mode 100644 integration/resources/expected/combination/function_with_deployment_basic.json create mode 100644 integration/resources/expected/combination/function_with_deployment_default_role_managed_policy.json create mode 100644 integration/resources/expected/combination/function_with_deployment_disabled.json create mode 100644 integration/resources/expected/combination/function_with_deployment_globals.json create mode 100644 integration/resources/expected/combination/function_with_dynamodb.json create mode 100644 integration/resources/expected/combination/function_with_file_system_config.json create mode 100644 integration/resources/expected/combination/function_with_http_api.json create mode 100644 integration/resources/expected/combination/function_with_implicit_api_and_conditions.json create mode 100644 integration/resources/expected/combination/function_with_implicit_http_api.json create mode 100644 integration/resources/expected/combination/function_with_kinesis.json create mode 100644 integration/resources/expected/combination/function_with_layer.json create mode 100644 integration/resources/expected/combination/function_with_mq.json create mode 100644 integration/resources/expected/combination/function_with_mq_using_autogen_role.json create mode 100644 integration/resources/expected/combination/function_with_msk.json create mode 100644 integration/resources/expected/combination/function_with_msk_using_managed_policy.json create mode 100644 integration/resources/expected/combination/function_with_policy_templates.json create mode 100644 integration/resources/expected/combination/function_with_resource_refs.json create mode 100644 integration/resources/expected/combination/function_with_s3.json create mode 100644 integration/resources/expected/combination/function_with_schedule.json create mode 100644 integration/resources/expected/combination/function_with_schedule_dlq_and_retry_policy.json create mode 100644 integration/resources/expected/combination/function_with_schedule_dlq_generated.json create mode 100644 integration/resources/expected/combination/function_with_signing_profile.json create mode 100644 integration/resources/expected/combination/function_with_sns.json create mode 100644 integration/resources/expected/combination/function_with_sqs.json create mode 100644 integration/resources/expected/combination/function_with_userpool_event.json create mode 100644 integration/resources/expected/combination/http_api_with_auth.json create mode 100644 integration/resources/expected/combination/http_api_with_cors.json create mode 100644 integration/resources/expected/combination/http_api_with_custom_domains_regional.json create mode 100644 integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_false.json create mode 100644 integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_true.json create mode 100644 integration/resources/expected/combination/implicit_api_with_settings.json create mode 100644 integration/resources/expected/combination/intrinsics_code_definition_uri.json create mode 100644 integration/resources/expected/combination/intrinsics_serverless_api.json create mode 100644 integration/resources/expected/combination/intrinsics_serverless_function.json create mode 100644 integration/resources/expected/combination/state_machine_with_api.json create mode 100644 integration/resources/expected/combination/state_machine_with_cwe.json create mode 100644 integration/resources/expected/combination/state_machine_with_cwe_dlq_generated.json create mode 100644 integration/resources/expected/combination/state_machine_with_cwe_with_dlq_and_retry_policy.json create mode 100644 integration/resources/expected/combination/state_machine_with_policy_templates.json create mode 100644 integration/resources/expected/combination/state_machine_with_schedule.json create mode 100644 integration/resources/expected/combination/state_machine_with_schedule_dlq_and_retry_policy.json create mode 100644 integration/resources/expected/combination/state_machine_with_schedule_dlq_generated.json create mode 100644 integration/resources/templates/combination/all_policy_templates.yaml create mode 100644 integration/resources/templates/combination/api_with_authorizers_invokefunction_set_none.yaml create mode 100644 integration/resources/templates/combination/api_with_authorizers_max.yaml create mode 100644 integration/resources/templates/combination/api_with_authorizers_max_openapi.yaml create mode 100644 integration/resources/templates/combination/api_with_authorizers_min.yaml create mode 100644 integration/resources/templates/combination/api_with_binary_media_types.yaml create mode 100644 integration/resources/templates/combination/api_with_binary_media_types_with_definition_body.yaml create mode 100644 integration/resources/templates/combination/api_with_binary_media_types_with_definition_body_openapi.yaml create mode 100644 integration/resources/templates/combination/api_with_cors.yaml create mode 100644 integration/resources/templates/combination/api_with_cors_only_headers.yaml create mode 100644 integration/resources/templates/combination/api_with_cors_only_max_age.yaml create mode 100644 integration/resources/templates/combination/api_with_cors_only_methods.yaml create mode 100644 integration/resources/templates/combination/api_with_cors_openapi.yaml create mode 100644 integration/resources/templates/combination/api_with_cors_shorthand.yaml create mode 100644 integration/resources/templates/combination/api_with_endpoint_configuration.yaml create mode 100644 integration/resources/templates/combination/api_with_endpoint_configuration_dict.yaml create mode 100644 integration/resources/templates/combination/api_with_gateway_responses.yaml create mode 100644 integration/resources/templates/combination/api_with_method_settings.yaml create mode 100644 integration/resources/templates/combination/api_with_request_models.yaml create mode 100644 integration/resources/templates/combination/api_with_request_models_openapi.yaml create mode 100644 integration/resources/templates/combination/api_with_request_parameters_openapi.yaml create mode 100644 integration/resources/templates/combination/api_with_resource_policies.yaml create mode 100644 integration/resources/templates/combination/api_with_resource_policies_aws_account.yaml create mode 100644 integration/resources/templates/combination/api_with_resource_refs.yaml create mode 100644 integration/resources/templates/combination/api_with_usage_plan.yaml create mode 100644 integration/resources/templates/combination/depends_on.yaml create mode 100644 integration/resources/templates/combination/function_with_alias.yaml create mode 100644 integration/resources/templates/combination/function_with_alias_and_event_sources.yaml create mode 100644 integration/resources/templates/combination/function_with_alias_globals.yaml create mode 100644 integration/resources/templates/combination/function_with_alias_intrinsics.yaml create mode 100644 integration/resources/templates/combination/function_with_all_event_types.yaml create mode 100644 integration/resources/templates/combination/function_with_all_event_types_condition_false.yaml create mode 100644 integration/resources/templates/combination/function_with_api.yaml create mode 100644 integration/resources/templates/combination/function_with_application.yaml create mode 100644 integration/resources/templates/combination/function_with_cloudwatch_log.yaml create mode 100644 integration/resources/templates/combination/function_with_custom_code_deploy.yaml create mode 100644 integration/resources/templates/combination/function_with_cwe_dlq_and_retry_policy.yaml create mode 100644 integration/resources/templates/combination/function_with_cwe_dlq_generated.yaml create mode 100644 integration/resources/templates/combination/function_with_deployment_alarms_and_hooks.yaml create mode 100644 integration/resources/templates/combination/function_with_deployment_basic.yaml create mode 100644 integration/resources/templates/combination/function_with_deployment_default_role_managed_policy.yaml create mode 100644 integration/resources/templates/combination/function_with_deployment_disabled.yaml create mode 100644 integration/resources/templates/combination/function_with_deployment_globals.yaml create mode 100644 integration/resources/templates/combination/function_with_dynamodb.yaml create mode 100644 integration/resources/templates/combination/function_with_file_system_config.yaml create mode 100644 integration/resources/templates/combination/function_with_http_api.yaml create mode 100644 integration/resources/templates/combination/function_with_implicit_api_and_conditions.yaml create mode 100644 integration/resources/templates/combination/function_with_implicit_http_api.yaml create mode 100644 integration/resources/templates/combination/function_with_kinesis.yaml create mode 100644 integration/resources/templates/combination/function_with_layer.yaml create mode 100644 integration/resources/templates/combination/function_with_mq.yaml create mode 100644 integration/resources/templates/combination/function_with_mq_using_autogen_role.yaml create mode 100644 integration/resources/templates/combination/function_with_msk.yaml create mode 100644 integration/resources/templates/combination/function_with_msk_using_managed_policy.yaml create mode 100644 integration/resources/templates/combination/function_with_policy_templates.yaml create mode 100644 integration/resources/templates/combination/function_with_resource_refs.yaml create mode 100644 integration/resources/templates/combination/function_with_s3.yaml create mode 100644 integration/resources/templates/combination/function_with_schedule.yaml create mode 100644 integration/resources/templates/combination/function_with_schedule_dlq_and_retry_policy.yaml create mode 100644 integration/resources/templates/combination/function_with_schedule_dlq_generated.yaml create mode 100644 integration/resources/templates/combination/function_with_signing_profile.yaml create mode 100644 integration/resources/templates/combination/function_with_sns.yaml create mode 100644 integration/resources/templates/combination/function_with_sqs.yaml create mode 100644 integration/resources/templates/combination/function_with_userpool_event.yaml create mode 100644 integration/resources/templates/combination/http_api_with_auth.yaml create mode 100644 integration/resources/templates/combination/http_api_with_auth_updated.yaml create mode 100644 integration/resources/templates/combination/http_api_with_cors.yaml create mode 100644 integration/resources/templates/combination/http_api_with_custom_domains_regional.yaml create mode 100644 integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_false.yaml create mode 100644 integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_true.yaml create mode 100644 integration/resources/templates/combination/implicit_api_with_settings.yaml create mode 100644 integration/resources/templates/combination/intrinsics_code_definition_uri.yaml create mode 100644 integration/resources/templates/combination/intrinsics_serverless_api.yaml create mode 100644 integration/resources/templates/combination/intrinsics_serverless_function.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_api.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_cwe.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_cwe_dlq_generated.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_cwe_with_dlq_and_retry_policy.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_policy_templates.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_schedule.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_schedule_dlq_and_retry_policy.yaml create mode 100644 integration/resources/templates/combination/state_machine_with_schedule_dlq_generated.yaml diff --git a/integration/combination/__init__.py b/integration/combination/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/combination/test_api_settings.py b/integration/combination/test_api_settings.py new file mode 100644 index 0000000000..7a9fd95745 --- /dev/null +++ b/integration/combination/test_api_settings.py @@ -0,0 +1,179 @@ +from io import BytesIO + +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +import requests +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + +from PIL import Image + + +class TestApiSettings(BaseTest): + def test_method_settings(self): + self.create_and_verify_stack("combination/api_with_method_settings") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + + wildcard_path = "*/*" + + method_settings = response["methodSettings"] + self.assertTrue(wildcard_path in method_settings, "MethodSettings for the wildcard path must be present") + + wildcard_path_setting = method_settings[wildcard_path] + + self.assertTrue(wildcard_path_setting["metricsEnabled"], "Metrics must be enabled") + self.assertTrue(wildcard_path_setting["dataTraceEnabled"], "DataTrace must be enabled") + self.assertEqual(wildcard_path_setting["loggingLevel"], "INFO", "LoggingLevel must be INFO") + + @parameterized.expand( + [ + "combination/api_with_binary_media_types", + "combination/api_with_binary_media_types_with_definition_body", + ] + ) + def test_binary_media_types(self, file_name): + self.create_and_verify_stack(file_name, self.get_default_test_template_parameters()) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + self.assertEqual(set(response["binaryMediaTypes"]), {"image/jpg", "image/png", "image/gif"}) + + @parameterized.expand( + [ + "combination/api_with_request_models", + "combination/api_with_request_models_openapi", + ] + ) + def test_request_models(self, file_name): + self.create_and_verify_stack(file_name) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_models(restApiId=rest_api_id) + request_models = response["items"] + + self.assertEqual(request_models[0]["name"], "user") + self.assertEqual( + request_models[0]["schema"], + '{\n "type" : "object",\n' + + ' "properties" : {\n "username" : {\n "type" : "string"\n }\n' + + " }\n}", + ) + + def test_request_parameters_open_api(self): + self.create_and_verify_stack("combination/api_with_request_parameters_openapi") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + # Test if the request parameters got set on the method + resources_response = apigw_client.get_resources(restApiId=rest_api_id) + resources = resources_response["items"] + + resource = get_resource_by_path(resources, "/one") + method = apigw_client.get_method(restApiId=rest_api_id, resourceId=resource["id"], httpMethod="GET") + expected = {"method.request.querystring.type": True} + self.assertEqual(expected, method["requestParameters"]) + + # Test that the method settings got applied on the method + stage_response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + method_settings = stage_response["methodSettings"] + + path = "one/GET" + self.assertTrue(path in method_settings, "MethodSettings for the path must be present") + + path_settings = method_settings[path] + self.assertEqual(path_settings["cacheTtlInSeconds"], 15) + self.assertTrue(path_settings["cachingEnabled"], "Caching must be enabled") + + def test_binary_media_types_with_definition_body_openapi(self): + parameters = self.get_default_test_template_parameters() + binary_media = { + "ParameterKey": "BinaryMediaCodeKey", + "ParameterValue": "binary-media.zip", + "UsePreviousValue": False, + "ResolvedValue": "string", + } + parameters.append(binary_media) + + self.create_and_verify_stack("combination/api_with_binary_media_types_with_definition_body_openapi", parameters) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + self.assertEqual( + set(response["binaryMediaTypes"]), {"image/jpg", "image/png", "image/gif", "application/octet-stream"} + ) + base_url = self.get_stack_output("ApiUrl")["OutputValue"] + self.verify_binary_media_request(base_url + "none", 200) + + @parameterized.expand( + [ + "combination/api_with_endpoint_configuration", + "combination/api_with_endpoint_configuration_dict", + ] + ) + def test_end_point_configuration(self, file_name): + self.create_and_verify_stack(file_name, self.get_default_test_template_parameters()) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + endpoint_config = response["endpointConfiguration"] + self.assertEqual(endpoint_config["types"], ["REGIONAL"]) + + def test_implicit_api_settings(self): + self.create_and_verify_stack("combination/implicit_api_with_settings") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + + wildcard_path = "*/*" + + method_settings = response["methodSettings"] + self.assertTrue(wildcard_path in method_settings, "MethodSettings for the wildcard path must be present") + + wildcard_path_setting = method_settings[wildcard_path] + + self.assertTrue(wildcard_path_setting["metricsEnabled"], "Metrics must be enabled") + self.assertTrue(wildcard_path_setting["dataTraceEnabled"], "DataTrace must be enabled") + self.assertEqual(wildcard_path_setting["loggingLevel"], "INFO", "LoggingLevel must be INFO") + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + endpoint_config = response["endpointConfiguration"] + self.assertEqual(endpoint_config["types"], ["REGIONAL"]) + self.assertEqual(set(response["binaryMediaTypes"]), {"image/jpg", "image/png"}) + + def verify_binary_media_request(self, url, expected_status_code): + headers = {"accept": "image/png"} + response = requests.get(url, headers=headers) + + status = response.status_code + expected = Image.open(Path(self.code_dir, "AWS_logo_RGB.png")) + + if 200 <= status <= 299: + actual = Image.open(BytesIO(response.content)) + self.assertEqual(expected, actual) + + self.assertEqual(status, expected_status_code, " must return HTTP " + str(expected_status_code)) + + +def get_resource_by_path(resources, path): + for resource in resources: + if resource["path"] == path: + return resource + return None diff --git a/integration/combination/test_api_with_authorizers.py b/integration/combination/test_api_with_authorizers.py new file mode 100644 index 0000000000..d08a27a470 --- /dev/null +++ b/integration/combination/test_api_with_authorizers.py @@ -0,0 +1,479 @@ +import requests + +from integration.helpers.base_test import BaseTest +from integration.helpers.deployer.utils.retry import retry +from integration.helpers.exception import StatusCodeError + + +class TestApiWithAuthorizers(BaseTest): + def test_authorizers_min(self): + self.create_and_verify_stack("combination/api_with_authorizers_min") + stack_outputs = self.get_stack_outputs() + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + authorizers = apigw_client.get_authorizers(restApiId=rest_api_id)["items"] + lambda_authorizer_uri = ( + "arn:aws:apigateway:" + + self.my_region + + ":lambda:path/2015-03-31/functions/" + + stack_outputs["AuthorizerFunctionArn"] + + "/invocations" + ) + + lambda_token_authorizer = get_authorizer_by_name(authorizers, "MyLambdaTokenAuth") + self.assertEqual(lambda_token_authorizer["type"], "TOKEN", "lambdaTokenAuthorizer: Type must be TOKEN") + self.assertEqual( + lambda_token_authorizer["identitySource"], + "method.request.header.Authorization", + "lambdaTokenAuthorizer: identity source must be method.request.header.Authorization", + ) + self.assertIsNone( + lambda_token_authorizer.get("authorizerCredentials"), + "lambdaTokenAuthorizer: authorizer credentials must not be set", + ) + self.assertIsNone( + lambda_token_authorizer.get("identityValidationExpression"), + "lambdaTokenAuthorizer: validation expression must not be set", + ) + self.assertEqual( + lambda_token_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaTokenAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertIsNone( + lambda_token_authorizer.get("authorizerResultTtlInSeconds"), "lambdaTokenAuthorizer: TTL must not be set" + ) + + lambda_request_authorizer = get_authorizer_by_name(authorizers, "MyLambdaRequestAuth") + self.assertEqual(lambda_request_authorizer["type"], "REQUEST", "lambdaRequestAuthorizer: Type must be REQUEST") + self.assertEqual( + lambda_request_authorizer["identitySource"], + "method.request.querystring.authorization", + "lambdaRequestAuthorizer: identity source must be method.request.querystring.authorization", + ) + self.assertIsNone( + lambda_request_authorizer.get("authorizerCredentials"), + "lambdaRequestAuthorizer: authorizer credentials must not be set", + ) + self.assertEqual( + lambda_request_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaRequestAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertIsNone( + lambda_request_authorizer.get("authorizerResultTtlInSeconds"), + "lambdaRequestAuthorizer: TTL must not be set", + ) + + cognito_authorizer = get_authorizer_by_name(authorizers, "MyCognitoAuthorizer") + cognito_user_pool_arn = stack_outputs["CognitoUserPoolArn"] + self.assertEqual( + cognito_authorizer["type"], "COGNITO_USER_POOLS", "cognitoAuthorizer: Type must be COGNITO_USER_POOLS" + ) + self.assertEqual( + cognito_authorizer["providerARNs"], + [cognito_user_pool_arn], + "cognitoAuthorizer: provider ARN must be the Cognito User Pool ARNs", + ) + self.assertIsNone( + cognito_authorizer.get("identityValidationExpression"), + "cognitoAuthorizer: validation expression must not be set", + ) + self.assertEqual( + cognito_authorizer["identitySource"], + "method.request.header.Authorization", + "cognitoAuthorizer: identity source must be method.request.header.Authorization", + ) + + resources = apigw_client.get_resources(restApiId=rest_api_id)["items"] + + lambda_token_get_method_result = get_method(resources, "/lambda-token", rest_api_id, apigw_client) + self.assertEqual( + lambda_token_get_method_result["authorizerId"], + lambda_token_authorizer["id"], + "lambdaTokenAuthorizer: GET method must be configured to use the Lambda Token Authorizer", + ) + + lambda_request_get_method_result = get_method(resources, "/lambda-request", rest_api_id, apigw_client) + self.assertEqual( + lambda_request_get_method_result["authorizerId"], + lambda_request_authorizer["id"], + "lambdaRequestAuthorizer: GET method must be configured to use the Lambda Request Authorizer", + ) + + cognito_get_method_result = get_method(resources, "/cognito", rest_api_id, apigw_client) + self.assertEqual( + cognito_get_method_result["authorizerId"], + cognito_authorizer["id"], + "cognitoAuthorizer: GET method must be configured to use the Cognito Authorizer", + ) + + iam_get_method_result = get_method(resources, "/iam", rest_api_id, apigw_client) + self.assertEqual( + iam_get_method_result["authorizationType"], + "AWS_IAM", + "iamAuthorizer: GET method must be configured to use AWS_IAM", + ) + + base_url = stack_outputs["ApiUrl"] + + self.verify_authorized_request(base_url + "none", 200) + self.verify_authorized_request(base_url + "lambda-token", 401) + self.verify_authorized_request(base_url + "lambda-token", 200, "Authorization", "allow") + + self.verify_authorized_request(base_url + "lambda-request", 401) + self.verify_authorized_request(base_url + "lambda-request?authorization=allow", 200) + + self.verify_authorized_request(base_url + "cognito", 401) + + self.verify_authorized_request(base_url + "iam", 403) + + def test_authorizers_max(self): + self.create_and_verify_stack("combination/api_with_authorizers_max") + stack_outputs = self.get_stack_outputs() + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + authorizers = apigw_client.get_authorizers(restApiId=rest_api_id)["items"] + lambda_authorizer_uri = ( + "arn:aws:apigateway:" + + self.my_region + + ":lambda:path/2015-03-31/functions/" + + stack_outputs["AuthorizerFunctionArn"] + + "/invocations" + ) + + lambda_token_authorizer = get_authorizer_by_name(authorizers, "MyLambdaTokenAuth") + self.assertEqual(lambda_token_authorizer["type"], "TOKEN", "lambdaTokenAuthorizer: Type must be TOKEN") + self.assertEqual( + lambda_token_authorizer["identitySource"], + "method.request.header.MyCustomAuthHeader", + "lambdaTokenAuthorizer: identity source must be method.request.header.MyCustomAuthHeader", + ) + self.assertEqual( + lambda_token_authorizer["authorizerCredentials"], + stack_outputs["LambdaAuthInvokeRoleArn"], + "lambdaTokenAuthorizer: authorizer credentials must be set", + ) + self.assertEqual( + lambda_token_authorizer["identityValidationExpression"], + "allow", + "lambdaTokenAuthorizer: validation expression must be set to allow", + ) + self.assertEqual( + lambda_token_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaTokenAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertEqual( + lambda_token_authorizer["authorizerResultTtlInSeconds"], 20, "lambdaTokenAuthorizer: TTL must be 20" + ) + + lambda_request_authorizer = get_authorizer_by_name(authorizers, "MyLambdaRequestAuth") + self.assertEqual(lambda_request_authorizer["type"], "REQUEST", "lambdaRequestAuthorizer: Type must be REQUEST") + self.assertEqual( + lambda_request_authorizer["identitySource"], + "method.request.header.authorizationHeader, method.request.querystring.authorization, method.request.querystring.authorizationQueryString1", + "lambdaRequestAuthorizer: identity source must be method.request.header.authorizationHeader, method.request.querystring.authorization, method.request.querystring.authorizationQueryString1", + ) + self.assertEqual( + lambda_request_authorizer["authorizerCredentials"], + stack_outputs["LambdaAuthInvokeRoleArn"], + "lambdaRequestAuthorizer: authorizer credentials must be set", + ) + self.assertEqual( + lambda_request_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaRequestAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertEqual( + lambda_request_authorizer["authorizerResultTtlInSeconds"], 0, "lambdaRequestAuthorizer: TTL must be 0" + ) + + cognito_authorizer = get_authorizer_by_name(authorizers, "MyCognitoAuthorizer") + cognito_user_pool_arn = stack_outputs["CognitoUserPoolArn"] + cognito_user_pool2_arn = stack_outputs["CognitoUserPoolTwoArn"] + self.assertEqual( + cognito_authorizer["type"], "COGNITO_USER_POOLS", "cognitoAuthorizer: Type must be COGNITO_USER_POOLS" + ) + self.assertEqual( + cognito_authorizer["providerARNs"], + [cognito_user_pool_arn, cognito_user_pool2_arn], + "cognitoAuthorizer: provider ARN must be the Cognito User Pool ARNs", + ) + self.assertEqual( + cognito_authorizer["identityValidationExpression"], + "myauthvalidationexpression", + "cognitoAuthorizer: validation expression must be set to myauthvalidationexpression", + ) + self.assertEqual( + cognito_authorizer["identitySource"], + "method.request.header.MyAuthorizationHeader", + "cognitoAuthorizer: identity source must be method.request.header.MyAuthorizationHeader", + ) + + resources = apigw_client.get_resources(restApiId=rest_api_id)["items"] + + lambda_token_get_method_result = get_method(resources, "/lambda-token", rest_api_id, apigw_client) + self.assertEqual( + lambda_token_get_method_result["authorizerId"], + lambda_token_authorizer["id"], + "lambdaTokenAuthorizer: GET method must be configured to use the Lambda Token Authorizer", + ) + + lambda_request_get_method_result = get_method(resources, "/lambda-request", rest_api_id, apigw_client) + self.assertEqual( + lambda_request_get_method_result["authorizerId"], + lambda_request_authorizer["id"], + "lambdaRequestAuthorizer: GET method must be configured to use the Lambda Request Authorizer", + ) + + cognito_get_method_result = get_method(resources, "/cognito", rest_api_id, apigw_client) + self.assertEqual( + cognito_get_method_result["authorizerId"], + cognito_authorizer["id"], + "cognitoAuthorizer: GET method must be configured to use the Cognito Authorizer", + ) + + iam_get_method_result = get_method(resources, "/iam", rest_api_id, apigw_client) + self.assertEqual( + iam_get_method_result["authorizationType"], + "AWS_IAM", + "iamAuthorizer: GET method must be configured to use AWS_IAM", + ) + + base_url = stack_outputs["ApiUrl"] + + self.verify_authorized_request(base_url + "none", 200) + self.verify_authorized_request(base_url + "lambda-token", 401) + self.verify_authorized_request(base_url + "lambda-token", 200, "MyCustomAuthHeader", "allow") + + self.verify_authorized_request(base_url + "lambda-request", 401) + self.verify_authorized_request( + base_url + "lambda-request?authorization=allow&authorizationQueryString1=x", 200, "authorizationHeader", "y" + ) + + self.verify_authorized_request(base_url + "cognito", 401) + + self.verify_authorized_request(base_url + "iam", 403) + + def test_authorizers_max_openapi(self): + self.create_and_verify_stack("combination/api_with_authorizers_max_openapi") + stack_outputs = self.get_stack_outputs() + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + authorizers = apigw_client.get_authorizers(restApiId=rest_api_id)["items"] + lambda_authorizer_uri = ( + "arn:aws:apigateway:" + + self.my_region + + ":lambda:path/2015-03-31/functions/" + + stack_outputs["AuthorizerFunctionArn"] + + "/invocations" + ) + + lambda_token_authorizer = get_authorizer_by_name(authorizers, "MyLambdaTokenAuth") + self.assertEqual(lambda_token_authorizer["type"], "TOKEN", "lambdaTokenAuthorizer: Type must be TOKEN") + self.assertEqual( + lambda_token_authorizer["identitySource"], + "method.request.header.MyCustomAuthHeader", + "lambdaTokenAuthorizer: identity source must be method.request.header.MyCustomAuthHeader", + ) + self.assertEqual( + lambda_token_authorizer["authorizerCredentials"], + stack_outputs["LambdaAuthInvokeRoleArn"], + "lambdaTokenAuthorizer: authorizer credentials must be set", + ) + self.assertEqual( + lambda_token_authorizer["identityValidationExpression"], + "allow", + "lambdaTokenAuthorizer: validation expression must be set to allow", + ) + self.assertEqual( + lambda_token_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaTokenAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertEqual( + lambda_token_authorizer["authorizerResultTtlInSeconds"], 20, "lambdaTokenAuthorizer: TTL must be 20" + ) + + lambda_request_authorizer = get_authorizer_by_name(authorizers, "MyLambdaRequestAuth") + self.assertEqual(lambda_request_authorizer["type"], "REQUEST", "lambdaRequestAuthorizer: Type must be REQUEST") + self.assertEqual( + lambda_request_authorizer["identitySource"], + "method.request.header.authorizationHeader, method.request.querystring.authorization, method.request.querystring.authorizationQueryString1", + "lambdaRequestAuthorizer: identity source must be method.request.header.authorizationHeader, method.request.querystring.authorization, method.request.querystring.authorizationQueryString1", + ) + self.assertEqual( + lambda_request_authorizer["authorizerCredentials"], + stack_outputs["LambdaAuthInvokeRoleArn"], + "lambdaRequestAuthorizer: authorizer credentials must be set", + ) + self.assertEqual( + lambda_request_authorizer["authorizerUri"], + lambda_authorizer_uri, + "lambdaRequestAuthorizer: authorizer URI must be the Lambda Function Authorizer's URI", + ) + self.assertEqual( + lambda_request_authorizer["authorizerResultTtlInSeconds"], 0, "lambdaRequestAuthorizer: TTL must be 0" + ) + + cognito_authorizer = get_authorizer_by_name(authorizers, "MyCognitoAuthorizer") + cognito_user_pool_arn = stack_outputs["CognitoUserPoolArn"] + cognito_user_pool2_arn = stack_outputs["CognitoUserPoolTwoArn"] + self.assertEqual( + cognito_authorizer["type"], "COGNITO_USER_POOLS", "cognitoAuthorizer: Type must be COGNITO_USER_POOLS" + ) + self.assertEqual( + cognito_authorizer["providerARNs"], + [cognito_user_pool_arn, cognito_user_pool2_arn], + "cognitoAuthorizer: provider ARN must be the Cognito User Pool ARNs", + ) + self.assertEqual( + cognito_authorizer["identityValidationExpression"], + "myauthvalidationexpression", + "cognitoAuthorizer: validation expression must be set to myauthvalidationexpression", + ) + self.assertEqual( + cognito_authorizer["identitySource"], + "method.request.header.MyAuthorizationHeader", + "cognitoAuthorizer: identity source must be method.request.header.MyAuthorizationHeader", + ) + + resources = apigw_client.get_resources(restApiId=rest_api_id)["items"] + + lambda_token_get_method_result = get_method(resources, "/lambda-token", rest_api_id, apigw_client) + self.assertEqual( + lambda_token_get_method_result["authorizerId"], + lambda_token_authorizer["id"], + "lambdaTokenAuthorizer: GET method must be configured to use the Lambda Token Authorizer", + ) + + lambda_request_get_method_result = get_method(resources, "/lambda-request", rest_api_id, apigw_client) + self.assertEqual( + lambda_request_get_method_result["authorizerId"], + lambda_request_authorizer["id"], + "lambdaRequestAuthorizer: GET method must be configured to use the Lambda Request Authorizer", + ) + + cognito_get_method_result = get_method(resources, "/cognito", rest_api_id, apigw_client) + self.assertEqual( + cognito_get_method_result["authorizerId"], + cognito_authorizer["id"], + "cognitoAuthorizer: GET method must be configured to use the Cognito Authorizer", + ) + + iam_get_method_result = get_method(resources, "/iam", rest_api_id, apigw_client) + self.assertEqual( + iam_get_method_result["authorizationType"], + "AWS_IAM", + "iamAuthorizer: GET method must be configured to use AWS_IAM", + ) + + base_url = stack_outputs["ApiUrl"] + + self.verify_authorized_request(base_url + "none", 200) + self.verify_authorized_request(base_url + "lambda-token", 401) + self.verify_authorized_request(base_url + "lambda-token", 200, "MyCustomAuthHeader", "allow") + + self.verify_authorized_request(base_url + "lambda-request", 401) + self.verify_authorized_request( + base_url + "lambda-request?authorization=allow&authorizationQueryString1=x", 200, "authorizationHeader", "y" + ) + + self.verify_authorized_request(base_url + "cognito", 401) + + self.verify_authorized_request(base_url + "iam", 403) + + api_key_id = stack_outputs["ApiKeyId"] + key = apigw_client.get_api_key(apiKey=api_key_id, includeValue=True) + + self.verify_authorized_request(base_url + "apikey", 200, "x-api-key", key["value"]) + self.verify_authorized_request(base_url + "apikey", 403) + + def test_authorizers_with_invoke_function_set_none(self): + self.create_and_verify_stack("combination/api_with_authorizers_invokefunction_set_none") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + resources = apigw_client.get_resources(restApiId=rest_api_id)["items"] + + function_with_invoke_role_default = get_method( + resources, "/MyFunctionDefaultInvokeRole", rest_api_id, apigw_client + ) + credentials_for_invoke_role_default = function_with_invoke_role_default["methodIntegration"]["credentials"] + self.assertEqual(credentials_for_invoke_role_default, "arn:aws:iam::*:user/*") + function_with_invoke_role_none = get_method(resources, "/MyFunctionNONEInvokeRole", rest_api_id, apigw_client) + credentials_for_invoke_role_none = function_with_invoke_role_none.get("methodIntegration").get( + "methodIntegration" + ) + self.assertIsNone(credentials_for_invoke_role_none) + + api_event_with_auth = get_method(resources, "/api/with-auth", rest_api_id, apigw_client) + auth_type_for_api_event_with_auth = api_event_with_auth["authorizationType"] + self.assertEqual(auth_type_for_api_event_with_auth, "AWS_IAM") + api_event_with_out_auth = get_method(resources, "/api/without-auth", rest_api_id, apigw_client) + auth_type_for_api_event_without_auth = api_event_with_out_auth["authorizationType"] + self.assertEqual(auth_type_for_api_event_without_auth, "NONE") + + @retry(StatusCodeError, 10) + def verify_authorized_request( + self, + url, + expected_status_code, + header_key=None, + header_value=None, + ): + if not header_key or not header_value: + response = requests.get(url) + else: + headers = {header_key: header_value} + response = requests.get(url, headers=headers) + status = response.status_code + if status != expected_status_code: + raise StatusCodeError( + "Request to {} failed with status: {}, expected status: {}".format(url, status, expected_status_code) + ) + + if not header_key or not header_value: + self.assertEqual( + status, expected_status_code, "Request to " + url + " must return HTTP " + str(expected_status_code) + ) + else: + self.assertEqual( + status, + expected_status_code, + "Request to " + + url + + " (" + + header_key + + ": " + + header_value + + ") must return HTTP " + + str(expected_status_code), + ) + + +def get_authorizer_by_name(authorizers, name): + for authorizer in authorizers: + if authorizer["name"] == name: + return authorizer + return None + + +def get_resource_by_path(resources, path): + for resource in resources: + if resource["path"] == path: + return resource + return None + + +def get_method(resources, path, rest_api_id, apigw_client): + resource = get_resource_by_path(resources, path) + return apigw_client.get_method(restApiId=rest_api_id, resourceId=resource["id"], httpMethod="GET") diff --git a/integration/combination/test_api_with_cors.py b/integration/combination/test_api_with_cors.py new file mode 100644 index 0000000000..abc3a5d06a --- /dev/null +++ b/integration/combination/test_api_with_cors.py @@ -0,0 +1,99 @@ +import requests + +from integration.helpers.base_test import BaseTest +from integration.helpers.deployer.utils.retry import retry +from parameterized import parameterized + +from integration.helpers.exception import StatusCodeError + +ALL_METHODS = "DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT" + + +class TestApiWithCors(BaseTest): + @parameterized.expand( + [ + "combination/api_with_cors", + "combination/api_with_cors_openapi", + ] + ) + def test_cors(self, file_name): + self.create_and_verify_stack(file_name) + + base_url = self.get_stack_outputs()["ApiUrl"] + + allow_methods = "methods" + allow_origin = "origins" + allow_headers = "headers" + max_age = "600" + + self.verify_options_request(base_url + "/apione", allow_methods, allow_origin, allow_headers, max_age) + self.verify_options_request(base_url + "/apitwo", allow_methods, allow_origin, allow_headers, max_age) + + def test_cors_with_shorthand_notation(self): + self.create_and_verify_stack("combination/api_with_cors_shorthand") + + base_url = self.get_stack_outputs()["ApiUrl"] + + allow_origin = "origins" + allow_headers = None # This should be absent from response + max_age = None # This should be absent from response + + self.verify_options_request(base_url + "/apione", ALL_METHODS, allow_origin, allow_headers, max_age) + self.verify_options_request(base_url + "/apitwo", "OPTIONS,POST", allow_origin, allow_headers, max_age) + + def test_cors_with_only_methods(self): + self.create_and_verify_stack("combination/api_with_cors_only_methods") + + base_url = self.get_stack_outputs()["ApiUrl"] + + allow_methods = "methods" + allow_origin = "*" + allow_headers = None # This should be absent from response + max_age = None # This should be absent from response + + self.verify_options_request(base_url + "/apione", allow_methods, allow_origin, allow_headers, max_age) + self.verify_options_request(base_url + "/apitwo", allow_methods, allow_origin, allow_headers, max_age) + + def test_cors_with_only_headers(self): + self.create_and_verify_stack("combination/api_with_cors_only_headers") + + base_url = self.get_stack_outputs()["ApiUrl"] + + allow_origin = "*" + allow_headers = "headers" + max_age = None # This should be absent from response + + self.verify_options_request(base_url + "/apione", ALL_METHODS, allow_origin, allow_headers, max_age) + self.verify_options_request(base_url + "/apitwo", "OPTIONS,POST", allow_origin, allow_headers, max_age) + + def test_cors_with_only_max_age(self): + self.create_and_verify_stack("combination/api_with_cors_only_max_age") + + base_url = self.get_stack_outputs()["ApiUrl"] + + allow_origin = "*" + allow_headers = None + max_age = "600" + + self.verify_options_request(base_url + "/apione", ALL_METHODS, allow_origin, allow_headers, max_age) + self.verify_options_request(base_url + "/apitwo", "OPTIONS,POST", allow_origin, allow_headers, max_age) + + @retry(StatusCodeError, 3) + def verify_options_request(self, url, allow_methods, allow_origin, allow_headers, max_age): + response = requests.options(url) + status = response.status_code + if status != 200: + raise StatusCodeError("Request to {} failed with status: {}, expected status: 200".format(url, status)) + + self.assertEqual(status, 200, "Options request must be successful and return HTTP 200") + headers = response.headers + self.assertEqual( + headers.get("Access-Control-Allow-Methods"), allow_methods, "Allow-Methods header must have proper value" + ) + self.assertEqual( + headers.get("Access-Control-Allow-Origin"), allow_origin, "Allow-Origin header must have proper value" + ) + self.assertEqual( + headers.get("Access-Control-Allow-Headers"), allow_headers, "Allow-Headers header must have proper value" + ) + self.assertEqual(headers.get("Access-Control-Max-Age"), max_age, "Max-Age header must have proper value") diff --git a/integration/combination/test_api_with_gateway_responses.py b/integration/combination/test_api_with_gateway_responses.py new file mode 100644 index 0000000000..1ff2efc76b --- /dev/null +++ b/integration/combination/test_api_with_gateway_responses.py @@ -0,0 +1,35 @@ +from integration.helpers.base_test import BaseTest + + +class TestApiWithGatewayResponses(BaseTest): + def test_gateway_responses(self): + self.create_and_verify_stack("combination/api_with_gateway_responses") + + stack_outputs = self.get_stack_outputs() + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + gateway_responses_result = apigw_client.get_gateway_responses(restApiId=rest_api_id) + gateway_responses = gateway_responses_result["items"] + gateway_response_type = "DEFAULT_4XX" + gateway_response = get_gateway_response_by_type(gateway_responses, gateway_response_type) + + self.assertEqual(gateway_response["defaultResponse"], False, "gatewayResponse: Default Response must be false") + self.assertEqual( + gateway_response["responseType"], + gateway_response_type, + "gatewayResponse: response type must be " + gateway_response_type, + ) + self.assertEqual(gateway_response.get("statusCode"), None, "gatewayResponse: status code must be none") + + base_url = stack_outputs["ApiUrl"] + response = self.verify_get_request_response(base_url + "iam", 403) + access_control_allow_origin = response.headers["Access-Control-Allow-Origin"] + self.assertEqual(access_control_allow_origin, "*", "Access-Control-Allow-Origin must be '*'") + + +def get_gateway_response_by_type(gateway_responses, gateway_response_type): + for response in gateway_responses: + if response["responseType"] == gateway_response_type: + return response + return None diff --git a/integration/combination/test_api_with_resource_policies.py b/integration/combination/test_api_with_resource_policies.py new file mode 100644 index 0000000000..5021de9cda --- /dev/null +++ b/integration/combination/test_api_with_resource_policies.py @@ -0,0 +1,196 @@ +import json + +from integration.helpers.base_test import BaseTest + + +class TestApiWithResourcePolicies(BaseTest): + def test_api_resource_policies(self): + self.create_and_verify_stack("combination/api_with_resource_policies") + + stack_outputs = self.get_stack_outputs() + region = stack_outputs["Region"] + accountId = stack_outputs["AccountId"] + partition = stack_outputs["Partition"] + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + rest_api_response = apigw_client.get_rest_api(restApiId=rest_api_id) + policy_str = rest_api_response["policy"] + + expected_policy_str = ( + '{"Version":"2012-10-17",' + + '"Statement":[{"Effect":"Allow",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/*\\/apione"},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/*\\/apione",' + + '"Condition":{"NotIpAddress":' + + '{"aws:SourceIp":"1.2.3.4"}}},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/*\\/apione",' + + '"Condition":{"StringNotEquals":' + + '{"aws:SourceVpc":"vpc-1234"}}},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/*\\/apione",' + + '"Condition":{"StringEquals":' + + '{"aws:SourceVpce":"vpce-5678"}}},' + + '{"Effect":"Allow",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/GET\\/apitwo"},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/GET\\/apitwo",' + + '"Condition":{"NotIpAddress":' + + '{"aws:SourceIp":"1.2.3.4"}}},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/GET\\/apitwo",' + + '"Condition":{"StringNotEquals":' + + '{"aws:SourceVpc":"vpc-1234"}}},' + + '{"Effect":"Deny",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/GET\\/apitwo",' + + '"Condition":{"StringEquals":' + + '{"aws:SourceVpce":"vpce-5678"}}},' + + '{"Effect":"Allow",' + + '"Principal":"*",' + + '"Action":"execute-api:Invoke",' + + '"Resource":"execute-api:*\\/*\\/*"}]}' + ) + + expected_policy = json.loads(expected_policy_str) + policy = json.loads(policy_str.encode().decode("unicode_escape")) + + self.assertTrue(self.compare_two_policies_object(policy, expected_policy)) + + def test_api_resource_policies_aws_account(self): + self.create_and_verify_stack("combination/api_with_resource_policies_aws_account") + + stack_outputs = self.get_stack_outputs() + region = stack_outputs["Region"] + accountId = stack_outputs["AccountId"] + partition = stack_outputs["Partition"] + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + rest_api_response = apigw_client.get_rest_api(restApiId=rest_api_id) + policy_str = rest_api_response["policy"] + + expected_policy_str = ( + '{\\"Version\\":\\"2012-10-17\\",' + + '\\"Statement\\":[{' + + '\\"Effect\\":\\"Allow\\",' + + '\\"Principal\\":{\\"AWS\\":\\"arn:' + + partition + + ":iam::" + + accountId + + ':root\\"},' + + '\\"Action\\":\\"execute-api:Invoke\\",' + + '\\"Resource\\":\\"arn:' + + partition + + ":execute-api:" + + region + + ":" + + accountId + + ":" + + rest_api_id + + '\\/Prod\\/GET\\/get\\"}]}' + ) + + self.assertEqual(policy_str, expected_policy_str) + + @staticmethod + def compare_two_policies_object(policy_a, policy_b): + if len(policy_a) != len(policy_b): + return False + + if policy_a["Version"] != policy_b["Version"]: + return False + + statement_a = policy_a["Statement"] + statement_b = policy_b["Statement"] + + if len(statement_a) != len(statement_b): + return False + + try: + for item in statement_a: + statement_b.remove(item) + except ValueError: + return False + return not statement_b diff --git a/integration/combination/test_api_with_usage_plan.py b/integration/combination/test_api_with_usage_plan.py new file mode 100644 index 0000000000..d77b16a681 --- /dev/null +++ b/integration/combination/test_api_with_usage_plan.py @@ -0,0 +1,22 @@ +from integration.helpers.base_test import BaseTest + + +class TestApiWithUsagePlan(BaseTest): + def test_api_with_usage_plans(self): + self.create_and_verify_stack("combination/api_with_usage_plan") + + outputs = self.get_stack_outputs() + apigw_client = self.client_provider.api_client + + serverless_usage_plan_id = outputs["ServerlessUsagePlan"] + my_api_usage_plan_id = outputs["MyApiUsagePlan"] + + serverless_usage_plan = apigw_client.get_usage_plan(usagePlanId=serverless_usage_plan_id) + my_api_usage_plan = apigw_client.get_usage_plan(usagePlanId=my_api_usage_plan_id) + + self.assertEqual(len(my_api_usage_plan["apiStages"]), 1) + self.assertEqual(my_api_usage_plan["throttle"]["burstLimit"], 100) + self.assertEqual(my_api_usage_plan["throttle"]["rateLimit"], 50.0) + self.assertEqual(my_api_usage_plan["quota"]["limit"], 500) + self.assertEqual(my_api_usage_plan["quota"]["period"], "MONTH") + self.assertEqual(len(serverless_usage_plan["apiStages"]), 2) diff --git a/integration/combination/test_depends_on.py b/integration/combination/test_depends_on.py new file mode 100644 index 0000000000..e605c5ff3c --- /dev/null +++ b/integration/combination/test_depends_on.py @@ -0,0 +1,8 @@ +from integration.helpers.base_test import BaseTest + + +class TestDependsOn(BaseTest): + def test_depends_on(self): + # Stack template is setup such that it will fail stack creation if DependsOn doesn't work. + # Simply creating the stack is enough verification + self.create_and_verify_stack("combination/depends_on") diff --git a/integration/combination/test_function_with_alias.py b/integration/combination/test_function_with_alias.py new file mode 100644 index 0000000000..8aab5a4727 --- /dev/null +++ b/integration/combination/test_function_with_alias.py @@ -0,0 +1,170 @@ +import json + +from botocore.exceptions import ClientError +from integration.helpers.base_test import BaseTest, LOG +from integration.helpers.common_api import get_function_versions + + +class TestFunctionWithAlias(BaseTest): + def test_updating_version_by_changing_property_value(self): + self.create_and_verify_stack("combination/function_with_alias") + alias_name = "Live" + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + version_ids = self.get_function_version_by_name(function_name) + self.assertEqual(["1"], version_ids) + + alias = self.get_alias(function_name, alias_name) + self.assertEqual("1", alias["FunctionVersion"]) + + # Changing CodeUri should create a new version, and leave the existing version in tact + self.set_template_resource_property("MyLambdaFunction", "CodeUri", self.file_to_s3_uri_map["code2.zip"]["uri"]) + self.transform_template() + self.deploy_stack() + + version_ids = self.get_function_version_by_name(function_name) + self.assertEqual(["1", "2"], version_ids) + + alias = self.get_alias(function_name, alias_name) + self.assertEqual("2", alias["FunctionVersion"]) + + # Make sure the stack has only One Version & One Alias resource + alias = self.get_stack_resources("AWS::Lambda::Alias") + versions = self.get_stack_resources("AWS::Lambda::Version") + self.assertEqual(len(alias), 1) + self.assertEqual(len(versions), 1) + + def test_alias_deletion_must_retain_version(self): + self.create_and_verify_stack("combination/function_with_alias") + alias_name = "Live" + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + version_ids = self.get_function_version_by_name(function_name) + self.assertEqual(["1"], version_ids) + + # Check that the DeletionPolicy on Lambda Version holds good + # Remove alias, update stack, and verify the version still exists by calling Lambda APIs + self.remove_template_resource_property("MyLambdaFunction", "AutoPublishAlias") + self.transform_template() + self.deploy_stack() + + # Make sure both Lambda version & alias resource does not exist in stack + alias = self.get_stack_resources("AWS::Lambda::Alias") + versions = self.get_stack_resources("AWS::Lambda::Version") + self.assertEqual(len(alias), 0) + self.assertEqual(len(versions), 0) + + # Make sure the version still exists in Lambda + version_ids = self.get_function_version_by_name(function_name) + self.assertEqual(["1"], version_ids) + + def test_function_with_alias_with_intrinsics(self): + parameters = self.get_default_test_template_parameters() + self.create_and_verify_stack("combination/function_with_alias_intrinsics", parameters) + alias_name = "Live" + + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + version_ids = get_function_versions(function_name, self.client_provider.lambda_client) + self.assertEqual(["1"], version_ids) + + alias = self.get_alias(function_name, alias_name) + self.assertEqual("1", alias["FunctionVersion"]) + + # Let's change Key by updating the template parameter, but keep template same + # This should create a new version and leave existing version intact + parameters[1]["ParameterValue"] = "code2.zip" + self.deploy_stack(parameters) + version_ids = get_function_versions(function_name, self.client_provider.lambda_client) + self.assertEqual(["1", "2"], version_ids) + + alias = self.get_alias(function_name, alias_name) + self.assertEqual("2", alias["FunctionVersion"]) + + def test_alias_in_globals_with_overrides(self): + # It is good enough if we can create a stack. Globals are pre-processed on the SAM template and don't + # add any extra runtime behavior that needs to be verified + self.create_and_verify_stack("combination/function_with_alias_globals") + + def test_alias_with_event_sources_get_correct_permissions(self): + # There are two parts to testing Event Source integrations: + # 1. Check if all event sources get wired to the alias + # 2. Check if Lambda::Permissions for the event sources are applied on the Alias + # + # This test checks #2 only because the former is easy to validate directly by looking at the CFN template in unit tests + # Also #1 requires calls to many different services which is hard. + self.create_and_verify_stack("combination/function_with_alias_and_event_sources") + alias_name = "Live" + + # Verify the permissions on the Alias are setup correctly. There should be as many resource policies as the Lambda::Permission resources + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + alias_arn = self.get_alias(function_name, alias_name)["AliasArn"] + permission_resources = self.get_stack_resources("AWS::Lambda::Permission") + + # Get the policies on both function & alias + # Alias should have as many policies as the Lambda::Permissions resource + alias_policy_str = self.get_function_policy(alias_arn) + alias_policy = json.loads(alias_policy_str) + self.assertIsNotNone(alias_policy.get("Statement")) + self.assertEqual(len(alias_policy["Statement"]), len(permission_resources)) + # Function should have *no* policies + function_policy_str = self.get_function_policy(function_name) + self.assertIsNone(function_policy_str) + + # Remove the alias, deploy the stack, and verify that *all* permission entities transfer to the function + self.remove_template_resource_property("MyAwesomeFunction", "AutoPublishAlias") + self.transform_template() + self.deploy_stack() + + # Get the policies on both function & alias + # Alias should have *no* policies + alias_policy_str = self.get_function_policy(alias_arn) + self.assertIsNone(alias_policy_str) + # Function should have as many policies as the Lambda::Permissions resource + function_policy_str = self.get_function_policy(function_name) + function_policy = json.loads(function_policy_str) + self.assertEqual(len(function_policy["Statement"]), len(permission_resources)) + + def get_function_version_by_name(self, function_name): + lambda_client = self.client_provider.lambda_client + versions = lambda_client.list_versions_by_function(FunctionName=function_name)["Versions"] + + # Exclude $LATEST from the list and simply return all the version numbers. + filtered_versions = [version["Version"] for version in versions if version["Version"] != "$LATEST"] + return filtered_versions + + def get_alias(self, function_name, alias_name): + lambda_client = self.client_provider.lambda_client + return lambda_client.get_alias(FunctionName=function_name, Name=alias_name) + + def get_function_policy(self, function_arn): + lambda_client = self.client_provider.lambda_client + try: + policy_result = lambda_client.get_policy(FunctionName=function_arn) + return policy_result["Policy"] + except ClientError as error: + if error.response["Error"]["Code"] == "ResourceNotFoundException": + LOG.debug("The resource you requested does not exist.") + return None + else: + raise error + + def get_default_test_template_parameters(self): + parameters = [ + { + "ParameterKey": "Bucket", + "ParameterValue": self.s3_bucket_name, + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "CodeKey", + "ParameterValue": "code.zip", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "SwaggerKey", + "ParameterValue": "swagger1.json", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + ] + return parameters diff --git a/integration/combination/test_function_with_all_event_types.py b/integration/combination/test_function_with_all_event_types.py new file mode 100644 index 0000000000..a8647003ca --- /dev/null +++ b/integration/combination/test_function_with_all_event_types.py @@ -0,0 +1,118 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithAllEventTypes(BaseTest): + def test_function_with_all_event_types(self): + self.create_and_verify_stack("combination/function_with_all_event_types") + + stack_outputs = self.get_stack_outputs() + + # make sure bucket notification configurations are added + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + actual_bucket_configuration_events = _get_actual_bucket_configuration_events(configurations) + + self.assertEqual(len(configurations), 2) + self.assertEqual(actual_bucket_configuration_events, {"s3:ObjectRemoved:*", "s3:ObjectCreated:*"}) + + # make sure two CW Events are created for MyAwesomeFunction + cloudwatch_events_client = self.client_provider.cloudwatch_event_client + lambda_client = self.client_provider.lambda_client + + my_awesome_function_name = self.get_physical_id_by_logical_id("MyAwesomeFunction") + alias_arn = lambda_client.get_alias(FunctionName=my_awesome_function_name, Name="Live")["AliasArn"] + + rule_names = cloudwatch_events_client.list_rule_names_by_target(TargetArn=alias_arn)["RuleNames"] + self.assertEqual(len(rule_names), 2) + + # make sure cloudwatch Schedule event has properties: name, state and description + schedule_name = stack_outputs["ScheduleName"] + cw_rule_result = cloudwatch_events_client.describe_rule(Name=schedule_name) + + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "DISABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(1 minute)") + + # make sure IOT Rule has lambda function action + iot_client = self.client_provider.iot_client + iot_rule_name = iot_client.list_topic_rules()["rules"][0]["ruleName"] + + action = iot_client.get_topic_rule(ruleName=iot_rule_name)["rule"]["actions"][0]["lambda"] + self.assertEqual(action["functionArn"], alias_arn) + + # Assert CloudWatch Logs group + log_client = self.client_provider.cloudwatch_log_client + cloud_watch_log_group_name = self.get_physical_id_by_type("AWS::Logs::LogGroup") + + subscription_filters_result = log_client.describe_subscription_filters(logGroupName=cloud_watch_log_group_name) + subscription_filter = subscription_filters_result["subscriptionFilters"][0] + self.assertEqual(len(subscription_filters_result["subscriptionFilters"]), 1) + self.assertTrue(alias_arn in subscription_filter["destinationArn"]) + self.assertEqual(subscription_filter["filterPattern"], "My pattern") + + # assert LambdaEventSourceMappings + event_source_mappings = lambda_client.list_event_source_mappings()["EventSourceMappings"] + event_source_mapping_configurations = [x for x in event_source_mappings if x["FunctionArn"] == alias_arn] + event_source_mapping_arns = set([x["EventSourceArn"] for x in event_source_mapping_configurations]) + + kinesis_client = self.client_provider.kinesis_client + kinesis_stream_name = self.get_physical_id_by_type("AWS::Kinesis::Stream") + kinesis_stream = kinesis_client.describe_stream(StreamName=kinesis_stream_name)["StreamDescription"] + + dynamo_db_stream_client = self.client_provider.dynamodb_streams_client + ddb_table_name = self.get_physical_id_by_type("AWS::DynamoDB::Table") + ddb_stream = dynamo_db_stream_client.list_streams(TableName=ddb_table_name)["Streams"][0] + + expected_mappings = {kinesis_stream["StreamARN"], ddb_stream["StreamArn"]} + self.assertEqual(event_source_mapping_arns, expected_mappings) + + kinesis_stream_config = next( + (x for x in event_source_mapping_configurations if x["EventSourceArn"] == kinesis_stream["StreamARN"]), None + ) + self.assertIsNotNone(kinesis_stream_config) + self.assertEqual(kinesis_stream_config["MaximumBatchingWindowInSeconds"], 20) + dynamo_db_stream_config = next( + (x for x in event_source_mapping_configurations if x["EventSourceArn"] == ddb_stream["StreamArn"]), None + ) + self.assertIsNotNone(dynamo_db_stream_config) + self.assertEqual(dynamo_db_stream_config["MaximumBatchingWindowInSeconds"], 20) + + # assert Notification Topic has lambda function endpoint + sns_client = self.client_provider.sns_client + sns_topic_arn = self.get_physical_id_by_type("AWS::SNS::Topic") + subscriptions_by_topic = sns_client.list_subscriptions_by_topic(TopicArn=sns_topic_arn)["Subscriptions"] + + self.assertEqual(len(subscriptions_by_topic), 1) + self.assertTrue(alias_arn in subscriptions_by_topic[0]["Endpoint"]) + self.assertEqual(subscriptions_by_topic[0]["Protocol"], "lambda") + self.assertEqual(subscriptions_by_topic[0]["TopicArn"], sns_topic_arn) + + def test_function_with_all_event_types_condition_false(self): + self.create_and_verify_stack("combination/function_with_all_event_types_condition_false") + + # make sure bucket notification configurations are added + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + actual_bucket_configuration_events = _get_actual_bucket_configuration_events(configurations) + + self.assertEqual(len(configurations), 1) + self.assertEqual(actual_bucket_configuration_events, {"s3:ObjectRemoved:*"}) + + +def _get_actual_bucket_configuration_events(configurations): + actual_bucket_configuration_events = set() + + for config in configurations: + for event in config.get("Events"): + actual_bucket_configuration_events.add(event) + + return actual_bucket_configuration_events diff --git a/integration/combination/test_function_with_api.py b/integration/combination/test_function_with_api.py new file mode 100644 index 0000000000..523aa5e46f --- /dev/null +++ b/integration/combination/test_function_with_api.py @@ -0,0 +1,31 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithApi(BaseTest): + def test_function_with_api(self): + self.create_and_verify_stack("combination/function_with_api") + + # Examine each resource policy and confirm that ARN contains correct APIGW stage + physical_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + + lambda_client = self.client_provider.lambda_client + policy_response = lambda_client.get_policy(FunctionName=lambda_function_name) + # This is a JSON string of resource policy + policy = policy_response["Policy"] + + # Instead of parsing the policy, we will verify that the policy contains certain strings + # that we would expect based on the resource policy + + # Paths are specified in the YAML template + get_api_policy_expectation = "{}/{}/{}/{}".format(physical_api_id, "*", "GET", "pathget") + post_api_policy_expectation = "{}/{}/{}/{}".format(physical_api_id, "*", "POST", "pathpost") + + self.assertTrue( + get_api_policy_expectation in policy, + "{} should be present in policy {}".format(get_api_policy_expectation, policy), + ) + self.assertTrue( + post_api_policy_expectation in policy, + "{} should be present in policy {}".format(post_api_policy_expectation, policy), + ) diff --git a/integration/combination/test_function_with_application.py b/integration/combination/test_function_with_application.py new file mode 100644 index 0000000000..e04219898c --- /dev/null +++ b/integration/combination/test_function_with_application.py @@ -0,0 +1,17 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithApplication(BaseTest): + def test_function_referencing_outputs_from_application(self): + self.create_and_verify_stack("combination/function_with_application") + + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + nested_stack_name = self.get_physical_id_by_type("AWS::CloudFormation::Stack") + lambda_client = self.client_provider.lambda_client + cfn_client = self.client_provider.cfn_client + + function_config = lambda_client.get_function_configuration(FunctionName=lambda_function_name) + stack_result = cfn_client.describe_stacks(StackName=nested_stack_name) + expected = stack_result["Stacks"][0]["Outputs"][0]["OutputValue"] + + self.assertEqual(function_config["Environment"]["Variables"]["TABLE_NAME"], expected) diff --git a/integration/combination/test_function_with_cloudwatch_log.py b/integration/combination/test_function_with_cloudwatch_log.py new file mode 100644 index 0000000000..e00974ba98 --- /dev/null +++ b/integration/combination/test_function_with_cloudwatch_log.py @@ -0,0 +1,19 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithCloudWatchLog(BaseTest): + def test_function_with_cloudwatch_log(self): + self.create_and_verify_stack("combination/function_with_cloudwatch_log") + + cloudwatch_log_group_name = self.get_physical_id_by_type("AWS::Logs::LogGroup") + lambda_function_endpoint = self.get_physical_id_by_type("AWS::Lambda::Function") + cloudwatch_log_client = self.client_provider.cloudwatch_log_client + + subscription_filter_result = cloudwatch_log_client.describe_subscription_filters( + logGroupName=cloudwatch_log_group_name + ) + subscription_filter = subscription_filter_result["subscriptionFilters"][0] + + self.assertEqual(len(subscription_filter_result["subscriptionFilters"]), 1) + self.assertTrue(lambda_function_endpoint in subscription_filter["destinationArn"]) + self.assertEqual(subscription_filter["filterPattern"], "My filter pattern") diff --git a/integration/combination/test_function_with_cwe_dlq_and_retry_policy.py b/integration/combination/test_function_with_cwe_dlq_and_retry_policy.py new file mode 100644 index 0000000000..17ce967100 --- /dev/null +++ b/integration/combination/test_function_with_cwe_dlq_and_retry_policy.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithCweDlqAndRetryPolicy(BaseTest): + def test_function_with_cwe(self): + # Verifying that following resources were created is correct + self.create_and_verify_stack("combination/function_with_cwe_dlq_and_retry_policy") + outputs = self.get_stack_outputs() + lambda_target_arn = outputs["MyLambdaArn"] + rule_name = outputs["MyEventName"] + lambda_target_dlq_arn = outputs["MyDLQArn"] + + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + # checking if the target's DLQ and RetryPolicy properties are correct + targets = cloud_watch_event_client.list_targets_by_rule(Rule=rule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + + target = targets[0] + self.assertEqual(target["Arn"], lambda_target_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], lambda_target_dlq_arn) + + self.assertEqual(target["RetryPolicy"]["MaximumEventAgeInSeconds"], 900) + self.assertEqual(target["RetryPolicy"]["MaximumRetryAttempts"], 6) diff --git a/integration/combination/test_function_with_cwe_dlq_generated.py b/integration/combination/test_function_with_cwe_dlq_generated.py new file mode 100644 index 0000000000..92491fd84f --- /dev/null +++ b/integration/combination/test_function_with_cwe_dlq_generated.py @@ -0,0 +1,66 @@ +import json + +from integration.helpers.base_test import BaseTest +from integration.helpers.resource import first_item_in_dict + + +class TestFunctionWithCweDlqGenerated(BaseTest): + def test_function_with_cwe(self): + # Verifying that following resources were created is correct + self.create_and_verify_stack("combination/function_with_cwe_dlq_generated") + outputs = self.get_stack_outputs() + lambda_target_arn = outputs["MyLambdaArn"] + rule_name = outputs["MyEventName"] + lambda_target_dlq_arn = outputs["MyDLQArn"] + lambda_target_dlq_url = outputs["MyDLQUrl"] + + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + cw_rule_result = cloud_watch_event_client.describe_rule(Name=rule_name) + # checking if the target has a dead-letter queue attached to it + targets = cloud_watch_event_client.list_targets_by_rule(Rule=rule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + + target = targets[0] + self.assertEqual(target["Arn"], lambda_target_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], lambda_target_dlq_arn) + + # checking if the generated dead-letter queue has necessary resource based policy attached to it + sqs_client = self.client_provider.sqs_client + dlq_policy_result = sqs_client.get_queue_attributes(QueueUrl=lambda_target_dlq_url, AttributeNames=["Policy"]) + dlq_policy_doc = dlq_policy_result["Attributes"]["Policy"] + dlq_policy = json.loads(dlq_policy_doc)["Statement"] + self.assertEqual(len(dlq_policy), 1, "Only one statement must be in Dead-letter queue policy") + dlq_policy_statement = dlq_policy[0] + + # checking policy action + actions = dlq_policy_statement["Action"] + action_list = actions if type(actions) == list else [actions] + self.assertEqual(len(action_list), 1, "Only one action must be in dead-letter queue policy") + self.assertEqual( + action_list[0], "sqs:SendMessage", "Action referenced in dead-letter queue policy must be 'sqs:SendMessage'" + ) + + # checking service principal + self.assertEqual(len(dlq_policy_statement["Principal"]), 1) + _, service_principal = first_item_in_dict(dlq_policy_statement["Principal"]) + self.assertEqual( + service_principal, + "events.amazonaws.com", + "Policy should grant EventBridge service principal to send messages to dead-letter queue", + ) + + # checking condition type + condition_type, condition_content = first_item_in_dict(dlq_policy_statement["Condition"]) + self.assertEqual(condition_type, "ArnEquals") + + # checking condition key + self.assertEqual(len(dlq_policy_statement["Condition"]), 1) + condition_key, condition_value = first_item_in_dict(condition_content) + self.assertEqual(condition_key, "aws:SourceArn") + + # checking condition value + self.assertEqual(len(condition_content), 1) + self.assertEqual( + condition_value, cw_rule_result["Arn"], "Policy should only allow requests coming from cwe rule resource" + ) diff --git a/integration/combination/test_function_with_deployment_preference.py b/integration/combination/test_function_with_deployment_preference.py new file mode 100644 index 0000000000..b2b2461ee6 --- /dev/null +++ b/integration/combination/test_function_with_deployment_preference.py @@ -0,0 +1,157 @@ +from integration.helpers.base_test import BaseTest + +CODEDEPLOY_APPLICATION_LOGICAL_ID = "ServerlessDeploymentApplication" +LAMBDA_FUNCTION_NAME = "MyLambdaFunction" +LAMBDA_ALIAS = "Live" + + +class TestFunctionWithDeploymentPreference(BaseTest): + def test_lambda_function_with_deployment_preference_uses_code_deploy(self): + self.create_and_verify_stack("combination/function_with_deployment_basic") + self._verify_no_deployment_then_update_and_verify_deployment() + + def test_lambda_function_with_custom_deployment_preference(self): + custom_deployment_config_name = "CustomLambdaDeploymentConfiguration" + # Want to delete / recreate custom deployment resource to make sure it exists and hasn't changed + if self._has_custom_deployment_configuration(custom_deployment_config_name): + self._delete_deployment_configuration(custom_deployment_config_name) + + self._create_deployment_configuration(custom_deployment_config_name) + + self.create_and_verify_stack("combination/function_with_custom_code_deploy") + self._verify_no_deployment_then_update_and_verify_deployment() + + def test_use_default_manage_policy(self): + self.create_and_verify_stack("combination/function_with_deployment_default_role_managed_policy") + self._verify_no_deployment_then_update_and_verify_deployment() + + def test_must_not_use_code_deploy(self): + self.create_and_verify_stack( + "combination/function_with_deployment_disabled", self.get_default_test_template_parameters() + ) + # When disabled, there should be no CodeDeploy resources created. This was already verified above + + def test_flip_from_disable_to_enable(self): + self.create_and_verify_stack( + "combination/function_with_deployment_disabled", self.get_default_test_template_parameters() + ) + + pref = self.get_template_resource_property("MyLambdaFunction", "DeploymentPreference") + pref["Enabled"] = "True" + self.set_template_resource_property("MyLambdaFunction", "DeploymentPreference", pref) + + self.transform_template() + self.deploy_stack(self.get_default_test_template_parameters()) + + self._verify_no_deployment_then_update_and_verify_deployment(self.get_default_test_template_parameters()) + + def test_must_deploy_with_alarms_and_hooks(self): + self.create_and_verify_stack("combination/function_with_deployment_alarms_and_hooks") + self._verify_no_deployment_then_update_and_verify_deployment() + + def test_deployment_preference_in_globals(self): + self.create_and_verify_stack("combination/function_with_deployment_globals") + application_name = self.get_physical_id_by_type("AWS::CodeDeploy::Application") + self.assertTrue(application_name in self._get_code_deploy_application()) + + deployment_groups = self._get_deployment_groups(application_name) + self.assertEqual(len(deployment_groups), 1) + + deployment_group_name = deployment_groups[0] + deployment_config_name = self._get_deployment_group_configuration_name(deployment_group_name, application_name) + self.assertEqual(deployment_config_name, "CodeDeployDefault.LambdaAllAtOnce") + + def _verify_no_deployment_then_update_and_verify_deployment(self, parameters=None): + application_name = self.get_physical_id_by_type("AWS::CodeDeploy::Application") + self.assertTrue(application_name in self._get_code_deploy_application()) + + deployment_groups = self._get_deployment_groups(application_name) + self.assertEqual(len(deployment_groups), 1) + + for deployment_group in deployment_groups: + # Verify no deployments for deployment group before we make change to code uri forcing lambda deployment + self.assertEqual(len(self._get_deployments(application_name, deployment_group)), 0) + + # Changing CodeUri should create a new version that deploys with CodeDeploy, and leave the existing version in stack + self.set_template_resource_property( + LAMBDA_FUNCTION_NAME, "CodeUri", self.file_to_s3_uri_map["code2.zip"]["uri"] + ) + self.transform_template() + self.deploy_stack(parameters) + + for deployment_group in deployment_groups: + deployments = self._get_deployments(application_name, deployment_group) + self.assertEqual(len(deployments), 1) + deployment_info = deployments[0] + self.assertEqual(deployment_info["status"], "Succeeded") + self._assert_deployment_contained_lambda_function_and_alias( + deployment_info, LAMBDA_FUNCTION_NAME, LAMBDA_ALIAS + ) + + def _get_code_deploy_application(self): + return self.client_provider.code_deploy_client.list_applications()["applications"] + + def _get_deployment_groups(self, application_name): + return self.client_provider.code_deploy_client.list_deployment_groups(applicationName=application_name)[ + "deploymentGroups" + ] + + def _get_deployments(self, application_name, deployment_group): + deployments = self.client_provider.code_deploy_client.list_deployments()["deployments"] + deployment_infos = [self._get_deployment_info(deployment_id) for deployment_id in deployments] + return deployment_infos + + def _get_deployment_info(self, deployment_id): + return self.client_provider.code_deploy_client.get_deployment(deploymentId=deployment_id)["deploymentInfo"] + + # Checks this deployment is connected to our specific lambda function and alias + def _assert_deployment_contained_lambda_function_and_alias( + self, deployment_info, lambda_function_name, lambda_alias + ): + instances = self._get_deployment_instances(deployment_info["deploymentId"]) + self.assertEqual(len(instances), 1) + # Instance Ids for lambda functions in CodeDeploy have the pattern : + function_colon_alias = instances[0] + self.assertTrue(":" in function_colon_alias) + + function_colon_alias_split = function_colon_alias.split(":") + self.assertEqual(len(function_colon_alias_split), 2) + self.assertEqual( + function_colon_alias_split[0], self._get_physical_resource_id("AWS::Lambda::Function", lambda_function_name) + ) + self.assertEqual(function_colon_alias_split[1], lambda_alias) + + def _get_deployment_instances(self, deployment_id): + return self.client_provider.code_deploy_client.list_deployment_instances(deploymentId=deployment_id)[ + "instancesList" + ] + + def _get_physical_resource_id(self, resource_type, logical_id): + resources_with_this_type = self.get_stack_resources(resource_type) + resources_with_this_id = next( + (x for x in resources_with_this_type if x["LogicalResourceId"] == logical_id), None + ) + return resources_with_this_id["PhysicalResourceId"] + + def _has_custom_deployment_configuration(self, deployment_name): + result = self.client_provider.code_deploy_client.list_deployment_configs()["deploymentConfigsList"] + return deployment_name in result + + def _delete_deployment_configuration(self, deployment_name): + self.client_provider.code_deploy_client.delete_deployment_config(deploymentConfigName=deployment_name) + + def _create_deployment_configuration(self, deployment_name): + client = self.client_provider.code_deploy_client + traffic_routing_config = { + "type": "TimeBasedLinear", + "timeBasedLinear": {"linearPercentage": 50, "linearInterval": 1}, + } + client.create_deployment_config( + deploymentConfigName=deployment_name, computePlatform="Lambda", trafficRoutingConfig=traffic_routing_config + ) + + def _get_deployment_group_configuration_name(self, deployment_group_name, application_name): + deployment_group = self.client_provider.code_deploy_client.get_deployment_group( + applicationName=application_name, deploymentGroupName=deployment_group_name + ) + return deployment_group["deploymentGroupInfo"]["deploymentConfigName"] diff --git a/integration/combination/test_function_with_dynamoDB.py b/integration/combination/test_function_with_dynamoDB.py new file mode 100644 index 0000000000..eb702679bc --- /dev/null +++ b/integration/combination/test_function_with_dynamoDB.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithDynamoDB(BaseTest): + def test_function_with_dynamoDB_trigger(self): + self.create_and_verify_stack("combination/function_with_dynamodb") + + ddb_id = self.get_physical_id_by_type("AWS::DynamoDB::Table") + dynamodb_streams_client = self.client_provider.dynamodb_streams_client + ddb_stream = dynamodb_streams_client.list_streams(TableName=ddb_id)["Streams"][0] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_arn = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_arn) + event_source_mapping_batch_size = event_source_mapping_result["BatchSize"] + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_dynamodb_stream_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_batch_size, 10) + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_dynamodb_stream_arn, ddb_stream["StreamArn"]) diff --git a/integration/combination/test_function_with_file_system_config.py b/integration/combination/test_function_with_file_system_config.py new file mode 100644 index 0000000000..48d357b5be --- /dev/null +++ b/integration/combination/test_function_with_file_system_config.py @@ -0,0 +1,6 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithFileSystemConfig(BaseTest): + def test_function_with_efs_integration(self): + self.create_and_verify_stack("combination/function_with_file_system_config") diff --git a/integration/combination/test_function_with_http_api.py b/integration/combination/test_function_with_http_api.py new file mode 100644 index 0000000000..139f3254a8 --- /dev/null +++ b/integration/combination/test_function_with_http_api.py @@ -0,0 +1,12 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithHttpApi(BaseTest): + def test_function_with_http_api(self): + self.create_and_verify_stack("combination/function_with_http_api") + + stack_outputs = self.get_stack_outputs() + base_url = stack_outputs["ApiUrl"] + self.verify_get_request_response(base_url + "some/path", 200) + self.verify_get_request_response(base_url + "something", 404) + self.verify_get_request_response(base_url + "another/endpoint", 404) diff --git a/integration/combination/test_function_with_implicit_api_and_conditions.py b/integration/combination/test_function_with_implicit_api_and_conditions.py new file mode 100644 index 0000000000..4c77fd2011 --- /dev/null +++ b/integration/combination/test_function_with_implicit_api_and_conditions.py @@ -0,0 +1,6 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithImplicitApiAndCondition(BaseTest): + def test_function_with_implicit_api_and_conditions(self): + self.create_and_verify_stack("combination/function_with_implicit_api_and_conditions") diff --git a/integration/combination/test_function_with_implicit_http_api.py b/integration/combination/test_function_with_implicit_http_api.py new file mode 100644 index 0000000000..fdaa8ebb96 --- /dev/null +++ b/integration/combination/test_function_with_implicit_http_api.py @@ -0,0 +1,12 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithImplicitHttpApi(BaseTest): + def test_function_with_implicit_api(self): + self.create_and_verify_stack("combination/function_with_implicit_http_api") + + stack_outputs = self.get_stack_outputs() + base_url = stack_outputs["ApiUrl"] + self.verify_get_request_response(base_url, 200) + self.verify_get_request_response(base_url + "something", 200) + self.verify_get_request_response(base_url + "another/endpoint", 200) diff --git a/integration/combination/test_function_with_kinesis.py b/integration/combination/test_function_with_kinesis.py new file mode 100644 index 0000000000..16f0adc5d4 --- /dev/null +++ b/integration/combination/test_function_with_kinesis.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithKinesis(BaseTest): + def test_function_with_kinesis_trigger(self): + self.create_and_verify_stack("combination/function_with_kinesis") + + kinesis_client = self.client_provider.kinesis_client + kinesis_id = self.get_physical_id_by_type("AWS::Kinesis::Stream") + kinesis_stream = kinesis_client.describe_stream(StreamName=kinesis_id)["StreamDescription"] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_arn = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_arn) + event_source_mapping_batch_size = event_source_mapping_result["BatchSize"] + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_kinesis_stream_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_batch_size, 100) + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_kinesis_stream_arn, kinesis_stream["StreamARN"]) diff --git a/integration/combination/test_function_with_layers.py b/integration/combination/test_function_with_layers.py new file mode 100644 index 0000000000..c75a509da6 --- /dev/null +++ b/integration/combination/test_function_with_layers.py @@ -0,0 +1,17 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithLayers(BaseTest): + def test_function_with_layer(self): + self.create_and_verify_stack("combination/function_with_layer") + + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_client = self.client_provider.lambda_client + + function_configuration_result = lambda_client.get_function_configuration(FunctionName=lambda_function_name) + + # Get the layer ARN from the stack and from the lambda function and verify they're the same + lambda_layer_version_arn = self.get_physical_id_by_type("AWS::Lambda::LayerVersion") + + lambda_function_layer_reference_arn = function_configuration_result["Layers"][0]["Arn"] + self.assertEqual(lambda_function_layer_reference_arn, lambda_layer_version_arn) diff --git a/integration/combination/test_function_with_mq.py b/integration/combination/test_function_with_mq.py new file mode 100644 index 0000000000..a87bddd75d --- /dev/null +++ b/integration/combination/test_function_with_mq.py @@ -0,0 +1,38 @@ +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithMq(BaseTest): + @parameterized.expand( + [ + "combination/function_with_mq", + "combination/function_with_mq_using_autogen_role", + ] + ) + def test_function_with_mq(self, file_name): + self.create_and_verify_stack(file_name) + + mq_client = self.client_provider.mq_client + mq_broker_id = self.get_physical_id_by_type("AWS::AmazonMQ::Broker") + broker_summary = get_broker_summary(mq_broker_id, mq_client) + + self.assertEqual(len(broker_summary), 1, "One MQ cluster should be present") + mq_broker_arn = broker_summary[0]["BrokerArn"] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_id = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_id) + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_mq_broker_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_mq_broker_arn, mq_broker_arn) + + +def get_broker_summary(mq_broker_id, mq_client): + broker_summaries = mq_client.list_brokers()["BrokerSummaries"] + return [broker_summary for broker_summary in broker_summaries if broker_summary["BrokerId"] == mq_broker_id] diff --git a/integration/combination/test_function_with_msk.py b/integration/combination/test_function_with_msk.py new file mode 100644 index 0000000000..cb855f1dba --- /dev/null +++ b/integration/combination/test_function_with_msk.py @@ -0,0 +1,34 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithMsk(BaseTest): + def test_function_with_msk_trigger(self): + self._common_validations_for_MSK("combination/function_with_msk") + + def test_function_with_msk_trigger_using_manage_policy(self): + self._common_validations_for_MSK("combination/function_with_msk_using_managed_policy") + + def _common_validations_for_MSK(self, file_name): + self.create_and_verify_stack(file_name) + + kafka_client = self.client_provider.kafka_client + + msk_cluster_id = self.get_physical_id_by_type("AWS::MSK::Cluster") + cluster_info_list = kafka_client.list_clusters()["ClusterInfoList"] + cluster_info = [x for x in cluster_info_list if x["ClusterArn"] == msk_cluster_id] + + self.assertEqual(len(cluster_info), 1, "One MSK cluster should be present") + + msk_cluster_arn = cluster_info[0]["ClusterArn"] + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_id = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_id) + + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_kafka_cluster_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_kafka_cluster_arn, msk_cluster_arn) diff --git a/integration/combination/test_function_with_s3_bucket.py b/integration/combination/test_function_with_s3_bucket.py new file mode 100644 index 0000000000..8e04d41d23 --- /dev/null +++ b/integration/combination/test_function_with_s3_bucket.py @@ -0,0 +1,18 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithS3Bucket(BaseTest): + def test_function_with_s3_bucket_trigger(self): + self.create_and_verify_stack("combination/function_with_s3") + + # Get the notification configuration and make sure Lambda Function connection is added + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + + # There should be only One notification configuration for the event + self.assertEqual(len(configurations), 1) + config = configurations[0] + self.assertEqual(set(config["Events"]), {"s3:ObjectCreated:*"}) diff --git a/integration/combination/test_function_with_schedule.py b/integration/combination/test_function_with_schedule.py new file mode 100644 index 0000000000..746355a38e --- /dev/null +++ b/integration/combination/test_function_with_schedule.py @@ -0,0 +1,20 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithSchedule(BaseTest): + def test_function_with_schedule(self): + self.create_and_verify_stack("combination/function_with_schedule") + + stack_outputs = self.get_stack_outputs() + + cloud_watch_events_client = self.client_provider.cloudwatch_event_client + + # get the cloudwatch schedule rule + schedule_name = stack_outputs["ScheduleName"] + cw_rule_result = cloud_watch_events_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "ENABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(5 minutes)") diff --git a/integration/combination/test_function_with_schedule_dlq_and_retry_policy.py b/integration/combination/test_function_with_schedule_dlq_and_retry_policy.py new file mode 100644 index 0000000000..663dfc0cbe --- /dev/null +++ b/integration/combination/test_function_with_schedule_dlq_and_retry_policy.py @@ -0,0 +1,31 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithScheduleDlqAndRetryPolicy(BaseTest): + def test_function_with_schedule(self): + self.create_and_verify_stack("combination/function_with_schedule_dlq_and_retry_policy") + + stack_outputs = self.get_stack_outputs() + schedule_name = stack_outputs["ScheduleName"] + lambda_target_dlq_arn = stack_outputs["MyDLQArn"] + + cloud_watch_events_client = self.client_provider.cloudwatch_event_client + + # get the cloudwatch schedule rule + cw_rule_result = cloud_watch_events_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "ENABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(5 minutes)") + + # checking if the target's DLQ and RetryPolicy properties are correct + targets = cloud_watch_events_client.list_targets_by_rule(Rule=schedule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + target = targets[0] + + self.assertEqual(target["DeadLetterConfig"]["Arn"], lambda_target_dlq_arn) + self.assertIsNone(target["RetryPolicy"].get("MaximumEventAgeInSeconds")) + self.assertEqual(target["RetryPolicy"]["MaximumRetryAttempts"], 10) diff --git a/integration/combination/test_function_with_schedule_dlq_generated.py b/integration/combination/test_function_with_schedule_dlq_generated.py new file mode 100644 index 0000000000..7d79937bc9 --- /dev/null +++ b/integration/combination/test_function_with_schedule_dlq_generated.py @@ -0,0 +1,84 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_queue_policy + + +class TestFunctionWithScheduleDlqGenerated(BaseTest): + def test_function_with_schedule(self): + self.create_and_verify_stack("combination/function_with_schedule_dlq_generated") + + stack_outputs = self.get_stack_outputs() + + schedule_name = stack_outputs["ScheduleName"] + lambda_target_arn = stack_outputs["MyLambdaArn"] + lambda_target_dlq_arn = stack_outputs["MyDLQArn"] + lambda_target_dlq_url = stack_outputs["MyDLQUrl"] + + cloud_watch_events_client = self.client_provider.cloudwatch_event_client + sqs_client = self.client_provider.sqs_client + + # get the cloudwatch schedule rule + cw_rule_result = cloud_watch_events_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "ENABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(5 minutes)") + + # checking if the target has a dead-letter queue attached to it + targets = cloud_watch_events_client.list_targets_by_rule(Rule=schedule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + target = targets[0] + + self.assertEqual(target["Arn"], lambda_target_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], lambda_target_dlq_arn) + + # checking if the generated dead-letter queue has necessary resource based policy attached to it + dlq_policy = get_queue_policy(lambda_target_dlq_url, sqs_client) + self.assertEqual(len(dlq_policy), 1, "Only one statement must be in Dead-letter queue policy") + dlq_policy_statement = dlq_policy[0] + + # checking policy action + self.assertFalse( + isinstance(dlq_policy_statement["Action"], list), "Only one action must be in dead-letter queue policy" + ) # if it is an array, it means has more than one action + self.assertEqual( + dlq_policy_statement["Action"], + "sqs:SendMessage", + "Action referenced in dead-letter queue policy must be 'sqs:SendMessage'", + ) + + # checking service principal + self.assertEqual( + len(dlq_policy_statement["Principal"]), + 1, + ) + self.assertEqual( + dlq_policy_statement["Principal"]["Service"], + "events.amazonaws.com", + "Policy should grant EventBridge service principal to send messages to dead-letter queue", + ) + + # checking condition type + key, value = get_first_key_value_pair_in_dict(dlq_policy_statement["Condition"]) + self.assertEqual(key, "ArnEquals") + + # checking condition key + self.assertEqual(len(dlq_policy_statement["Condition"]), 1) + condition_kay, condition_value = get_first_key_value_pair_in_dict(value) + self.assertEqual(condition_kay, "aws:SourceArn") + + # checking condition value + self.assertEqual(len(dlq_policy_statement["Condition"][key]), 1) + self.assertEqual( + condition_value, + cw_rule_result["Arn"], + "Policy should only allow requests coming from schedule rule resource", + ) + + +def get_first_key_value_pair_in_dict(dictionary): + key = list(dictionary.keys())[0] + value = dictionary[key] + return key, value diff --git a/integration/combination/test_function_with_signing_profile.py b/integration/combination/test_function_with_signing_profile.py new file mode 100644 index 0000000000..f83f908361 --- /dev/null +++ b/integration/combination/test_function_with_signing_profile.py @@ -0,0 +1,6 @@ +from integration.helpers.base_test import BaseTest + + +class TestDependsOn(BaseTest): + def test_depends_on(self): + self.create_and_verify_stack("combination/function_with_signing_profile") diff --git a/integration/combination/test_function_with_sns.py b/integration/combination/test_function_with_sns.py new file mode 100644 index 0000000000..fa90c1ee84 --- /dev/null +++ b/integration/combination/test_function_with_sns.py @@ -0,0 +1,28 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithSns(BaseTest): + def test_function_with_sns_bucket_trigger(self): + self.create_and_verify_stack("combination/function_with_sns") + + sns_client = self.client_provider.sns_client + + sns_topic_arn = self.get_physical_id_by_type("AWS::SNS::Topic") + lambda_function_endpoint = self.get_physical_id_by_type("AWS::Lambda::Function") + + subscriptions = sns_client.list_subscriptions_by_topic(TopicArn=sns_topic_arn)["Subscriptions"] + self.assertEqual(len(subscriptions), 2) + + # checks if SNS has two subscriptions: lambda and SQS + lambda_subscription = next((x for x in subscriptions if x["Protocol"] == "lambda"), None) + + self.assertIsNotNone(lambda_subscription) + self.assertTrue(lambda_function_endpoint in lambda_subscription["Endpoint"]) + self.assertEqual(lambda_subscription["Protocol"], "lambda") + self.assertEqual(lambda_subscription["TopicArn"], sns_topic_arn) + + sqs_subscription = next((x for x in subscriptions if x["Protocol"] == "sqs"), None) + + self.assertIsNotNone(sqs_subscription) + self.assertEqual(sqs_subscription["Protocol"], "sqs") + self.assertEqual(sqs_subscription["TopicArn"], sns_topic_arn) diff --git a/integration/combination/test_function_with_sqs.py b/integration/combination/test_function_with_sqs.py new file mode 100644 index 0000000000..e5f54cc2d4 --- /dev/null +++ b/integration/combination/test_function_with_sqs.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithSns(BaseTest): + def test_function_with_sns_bucket_trigger(self): + self.create_and_verify_stack("combination/function_with_sqs") + + sqs_client = self.client_provider.sqs_client + sqs_queue_url = self.get_physical_id_by_type("AWS::SQS::Queue") + queue_attributes = sqs_client.get_queue_attributes(QueueUrl=sqs_queue_url, AttributeNames=["QueueArn"])[ + "Attributes" + ] + sqs_queue_arn = queue_attributes["QueueArn"] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_arn = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_arn) + + self.assertEqual(event_source_mapping_result["BatchSize"], 2) + self.assertEqual(event_source_mapping_result["FunctionArn"], lambda_function_arn) + self.assertEqual(event_source_mapping_result["EventSourceArn"], sqs_queue_arn) diff --git a/integration/combination/test_function_with_user_pool_event.py b/integration/combination/test_function_with_user_pool_event.py new file mode 100644 index 0000000000..ba9ca9f265 --- /dev/null +++ b/integration/combination/test_function_with_user_pool_event.py @@ -0,0 +1,11 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithUserPoolEvent(BaseTest): + def test_function_with_user_pool_event(self): + self.create_and_verify_stack("combination/function_with_userpool_event") + lambda_resources = self.get_stack_resources("AWS::Lambda::Permission") + my_function_cognito_permission = next( + (x for x in lambda_resources if x["LogicalResourceId"] == "PreSignupLambdaFunctionCognitoPermission"), None + ) + self.assertIsNotNone(my_function_cognito_permission) diff --git a/integration/combination/test_http_api_with_auth.py b/integration/combination/test_http_api_with_auth.py new file mode 100644 index 0000000000..963e447388 --- /dev/null +++ b/integration/combination/test_http_api_with_auth.py @@ -0,0 +1,83 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithUserPoolEvent(BaseTest): + def test_function_with_user_pool_event(self): + self.create_and_verify_stack("combination/http_api_with_auth") + + http_api_list = self.get_stack_resources("AWS::ApiGatewayV2::Api") + self.assertEqual(len(http_api_list), 1) + + http_resource = http_api_list[0] + http_api_id = http_resource["PhysicalResourceId"] + api_v2_client = self.client_provider.api_v2_client + authorizer_list = api_v2_client.get_authorizers(ApiId=http_api_id)["Items"] + + self.assertEqual(len(authorizer_list), 2) + + lambda_auth = next(x for x in authorizer_list if x["Name"] == "MyLambdaAuth") + self.assertEqual(lambda_auth["AuthorizerType"], "REQUEST") + self.assertEqual(lambda_auth["AuthorizerPayloadFormatVersion"], "2.0") + self.assertTrue(lambda_auth["EnableSimpleResponses"]) + lambda_identity_source_list = lambda_auth["IdentitySource"] + self.assertEqual(len(lambda_identity_source_list), 4) + self.assertTrue("$request.header.Authorization" in lambda_identity_source_list) + self.assertTrue("$request.querystring.petId" in lambda_identity_source_list) + self.assertTrue("$stageVariables.stageVar" in lambda_identity_source_list) + self.assertTrue("$context.contextVar" in lambda_identity_source_list) + + role_resources = self.get_stack_resources("AWS::IAM::Role") + self.assertEqual(len(role_resources), 2) + auth_role = next((x for x in role_resources if x["LogicalResourceId"] == "MyAuthFnRole"), None) + auth_role_arn = auth_role["PhysicalResourceId"] + self.assertTrue(auth_role_arn in lambda_auth["AuthorizerCredentialsArn"]) + + # Format of AuthorizerUri is in format of /2015-03-31/functions/[FunctionARN]/invocations + function_resources = self.get_stack_resources("AWS::Lambda::Function") + self.assertEqual(len(function_resources), 2) + auth_function = next((x for x in function_resources if x["LogicalResourceId"] == "MyAuthFn"), None) + auth_function_arn = auth_function["PhysicalResourceId"] + self.assertTrue(auth_function_arn in lambda_auth["AuthorizerUri"]) + self.assertEqual(lambda_auth["AuthorizerResultTtlInSeconds"], 23) + + oauth_2_auth = next((x for x in authorizer_list if x["Name"] == "MyOAuth2Auth"), None) + self.assertEqual(oauth_2_auth["AuthorizerType"], "JWT") + jwt_configuration = oauth_2_auth["JwtConfiguration"] + self.assertEqual(jwt_configuration["Issuer"], "https://openid-connect.onelogin.com/oidc") + self.assertEqual(len(jwt_configuration["Audience"]), 1) + self.assertEqual(jwt_configuration["Audience"][0], "MyApi") + self.assertEqual(len(oauth_2_auth["IdentitySource"]), 1) + self.assertEqual(oauth_2_auth["IdentitySource"][0], "$request.querystring.param") + + # Test updating stack + self.update_stack("combination/http_api_with_auth_updated") + + http_api_list_updated = self.get_stack_resources("AWS::ApiGatewayV2::Api") + self.assertEqual(len(http_api_list_updated), 1) + + http_resource_updated = http_api_list_updated[0] + http_api_id_updated = http_resource_updated["PhysicalResourceId"] + authorizer_list_updated = api_v2_client.get_authorizers(ApiId=http_api_id_updated)["Items"] + self.assertEqual(len(authorizer_list_updated), 1) + + lambda_auth_updated = next(x for x in authorizer_list_updated if x["Name"] == "MyLambdaAuthUpdated") + self.assertEqual(lambda_auth_updated["AuthorizerType"], "REQUEST") + self.assertEqual(lambda_auth_updated["AuthorizerPayloadFormatVersion"], "1.0") + self.assertEqual(lambda_auth_updated["AuthorizerResultTtlInSeconds"], 37) + lambda_identity_source_list_updated = lambda_auth_updated["IdentitySource"] + self.assertEqual(len(lambda_identity_source_list_updated), 1) + self.assertTrue("$request.header.Authorization" in lambda_identity_source_list_updated) + + role_resources_updated = self.get_stack_resources("AWS::IAM::Role") + self.assertEqual(len(role_resources_updated), 2) + auth_role_updated = next((x for x in role_resources_updated if x["LogicalResourceId"] == "MyAuthFnRole"), None) + auth_role_arn_updated = auth_role_updated["PhysicalResourceId"] + self.assertTrue(auth_role_arn_updated in lambda_auth_updated["AuthorizerCredentialsArn"]) + + function_resources_updated = self.get_stack_resources("AWS::Lambda::Function") + self.assertEqual(len(function_resources_updated), 2) + auth_function_updated = next( + (x for x in function_resources_updated if x["LogicalResourceId"] == "MyAuthFn"), None + ) + auth_function_arn_updated = auth_function_updated["PhysicalResourceId"] + self.assertTrue(auth_function_arn_updated in lambda_auth_updated["AuthorizerUri"]) diff --git a/integration/combination/test_http_api_with_cors.py b/integration/combination/test_http_api_with_cors.py new file mode 100644 index 0000000000..a51db83033 --- /dev/null +++ b/integration/combination/test_http_api_with_cors.py @@ -0,0 +1,43 @@ +from integration.helpers.base_test import BaseTest + + +class TestHttpApiWithCors(BaseTest): + def test_cors(self): + self.create_and_verify_stack("combination/http_api_with_cors") + + api_2_client = self.client_provider.api_v2_client + api_id = self.get_stack_outputs()["ApiId"] + api_result = api_2_client.get_api(ApiId=api_id) + + # verifying in cors configuration is set correctly at api level + cors_configuration = api_result["CorsConfiguration"] + self.assertEqual(cors_configuration["AllowMethods"], ["GET"], "Allow-Methods must have proper value") + self.assertEqual( + cors_configuration["AllowOrigins"], ["https://foo.com"], "Allow-Origins must have proper value" + ) + self.assertEqual( + cors_configuration["AllowHeaders"], ["x-apigateway-header"], "Allow-Headers must have proper value" + ) + self.assertEqual( + cors_configuration["ExposeHeaders"], ["x-amzn-header"], "Expose-Headers must have proper value" + ) + self.assertIsNone(cors_configuration.get("MaxAge"), "Max-Age must be null as it is not set in the template") + self.assertIsNone( + cors_configuration.get("AllowCredentials"), + "Allow-Credentials must be null as it is not set in the template", + ) + + # Every HttpApi should have a default tag created by SAM (httpapi:createdby: SAM) + tags = api_result["Tags"] + self.assertEqual(len(tags), 1) + self.assertEqual(tags["httpapi:createdBy"], "SAM") + + # verifying if TimeoutInMillis is set properly in the integration + integrations = api_2_client.get_integrations(ApiId=api_id)["Items"] + self.assertEqual(len(integrations), 1) + self.assertEqual( + integrations[0]["TimeoutInMillis"], 15000, "valid integer value must be given for timeout in millis" + ) + self.assertEqual( + integrations[0]["PayloadFormatVersion"], "1.0", "valid string must be given for payload format version" + ) diff --git a/integration/combination/test_http_api_with_disable_execute_api_endpoint.py b/integration/combination/test_http_api_with_disable_execute_api_endpoint.py new file mode 100644 index 0000000000..3012e1b85d --- /dev/null +++ b/integration/combination/test_http_api_with_disable_execute_api_endpoint.py @@ -0,0 +1,18 @@ +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + + +class TestHttpApiWithDisableExecuteApiEndpoint(BaseTest): + @parameterized.expand( + [ + ("combination/http_api_with_disable_execute_api_endpoint_true", True), + ("combination/http_api_with_disable_execute_api_endpoint_false", False), + ] + ) + def test_disable_execute_api_endpoint_true(self, file_name, is_disable): + self.create_and_verify_stack(file_name) + api_2_client = self.client_provider.api_v2_client + api_id = self.get_stack_outputs()["ApiId"] + api_result = api_2_client.get_api(ApiId=api_id) + self.assertEqual(api_result["DisableExecuteApiEndpoint"], is_disable) diff --git a/integration/combination/test_intrinsic_function_support.py b/integration/combination/test_intrinsic_function_support.py new file mode 100644 index 0000000000..05d9cd5ced --- /dev/null +++ b/integration/combination/test_intrinsic_function_support.py @@ -0,0 +1,60 @@ +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + + +class TestIntrinsicFunctionsSupport(BaseTest): + + # test code definition uri object and serverless function properties support + @parameterized.expand( + [ + "combination/intrinsics_code_definition_uri", + "combination/intrinsics_serverless_function", + ] + ) + def test_common_support(self, file_name): + # Just a simple deployment will validate that Code & Swagger files were accessible + # Just a simple deployment will validate that all properties were resolved expected + self.create_and_verify_stack(file_name, self.get_default_test_template_parameters()) + + def test_severless_api_properties_support(self): + self.create_and_verify_stack( + "combination/intrinsics_serverless_api", self.get_default_test_template_parameters() + ) + + # Examine each resource policy and confirm that ARN contains correct APIGW stage + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + + lambda_client = self.client_provider.lambda_client + + # This is a JSON string of resource policy + policy = lambda_client.get_policy(FunctionName=lambda_function_name)["Policy"] + + # Instead of parsing the policy, we will verify that the policy contains certain strings + # that we would expect based on the resource policy + + # This is the stage name specified in YAML template + api_stage_name = "devstage" + + # Paths are specififed in the YAML template + get_api_policy_expectation = "*/GET/pathget" + post_api_policy_expectation = "*/POST/pathpost" + + self.assertTrue( + get_api_policy_expectation in policy, + "{} should be present in policy {}".format(get_api_policy_expectation, policy), + ) + self.assertTrue( + post_api_policy_expectation in policy, + "{} should be present in policy {}".format(post_api_policy_expectation, policy), + ) + + # Test for tags + function_result = lambda_client.get_function(FunctionName=lambda_function_name) + tags = function_result["Tags"] + + self.assertIsNotNone(tags, "Expecting tags on function.") + self.assertTrue("lambda:createdBy" in tags, "Expected 'lambda:CreatedBy' tag key, but not found.") + self.assertEqual(tags["lambda:createdBy"], "SAM", "Expected 'SAM' tag value, but not found.") + self.assertTrue("TagKey1" in tags) + self.assertEqual(tags["TagKey1"], api_stage_name) diff --git a/integration/combination/test_resource_references.py b/integration/combination/test_resource_references.py new file mode 100644 index 0000000000..e7221ea414 --- /dev/null +++ b/integration/combination/test_resource_references.py @@ -0,0 +1,49 @@ +from integration.helpers.base_test import BaseTest + + +from integration.helpers.common_api import get_function_versions + + +# Tests resource references support of SAM Function resource +class TestResourceReferences(BaseTest): + def test_function_alias_references(self): + self.create_and_verify_stack("combination/function_with_resource_refs") + + lambda_client = self.client_provider.lambda_client + functions = self.get_stack_resources("AWS::Lambda::Function") + function_names = [x["PhysicalResourceId"] for x in functions] + + main_function_name = next((x for x in function_names if "MyLambdaFunction" in x), None) + other_function_name = next((x for x in function_names if "MyOtherFunction" in x), None) + + alias_result = lambda_client.get_alias(FunctionName=main_function_name, Name="Live") + alias_arn = alias_result["AliasArn"] + version_number = get_function_versions(main_function_name, lambda_client)[0] + version_arn = lambda_client.get_function_configuration( + FunctionName=main_function_name, Qualifier=version_number + )["FunctionArn"] + + # Make sure the AliasArn is injected properly in all places where it is referenced + other_function_env_var_result = lambda_client.get_function_configuration(FunctionName=other_function_name) + other_function_env_var = other_function_env_var_result["Environment"]["Variables"]["AliasArn"] + self.assertEqual(other_function_env_var, alias_arn) + + # Grab outputs from the stack + stack_outputs = self.get_stack_outputs() + self.assertEqual(stack_outputs["AliasArn"], alias_arn) + self.assertEqual(stack_outputs["AliasInSub"], alias_arn + " Alias") + self.assertEqual(stack_outputs["VersionNumber"], version_number) + self.assertEqual(stack_outputs["VersionArn"], version_arn) + + def test_api_with_resource_references(self): + self.create_and_verify_stack("combination/api_with_resource_refs") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + + apigw_client = self.client_provider.api_client + stage_result = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + + stack_outputs = self.get_stack_outputs() + self.assertEqual(stack_outputs["StageName"], "Prod") + self.assertEqual(stack_outputs["ApiId"], rest_api_id) + self.assertEqual(stack_outputs["DeploymentId"], stage_result["deploymentId"]) diff --git a/integration/combination/test_state_machine_with_api.py b/integration/combination/test_state_machine_with_api.py new file mode 100644 index 0000000000..a09f06f689 --- /dev/null +++ b/integration/combination/test_state_machine_with_api.py @@ -0,0 +1,88 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements + + +class TestStateMachineWithApi(BaseTest): + def test_state_machine_with_api(self): + self.create_and_verify_stack("combination/state_machine_with_api") + outputs = self.get_stack_outputs() + region = outputs["Region"] + partition = outputs["Partition"] + state_name_machine_arn = outputs["MyStateMachineArn"] + implicit_api_role_name = outputs["MyImplicitApiRoleName"] + implicit_api_role_arn = outputs["MyImplicitApiRoleArn"] + explicit_api_role_name = outputs["MyExplicitApiRoleName"] + explicit_api_role_arn = outputs["MyExplicitApiRoleArn"] + + rest_apis = self.get_stack_resources("AWS::ApiGateway::RestApi") + implicit_rest_api_id = next( + (x["PhysicalResourceId"] for x in rest_apis if x["LogicalResourceId"] == "ServerlessRestApi"), None + ) + explicit_rest_api_id = next( + (x["PhysicalResourceId"] for x in rest_apis if x["LogicalResourceId"] == "ExistingRestApi"), None + ) + + self._test_api_integration_with_state_machine( + implicit_rest_api_id, + "POST", + "/pathpost", + implicit_api_role_name, + implicit_api_role_arn, + "MyStateMachinePostApiRoleStartExecutionPolicy", + state_name_machine_arn, + partition, + region, + ) + self._test_api_integration_with_state_machine( + explicit_rest_api_id, + "GET", + "/pathget", + explicit_api_role_name, + explicit_api_role_arn, + "MyStateMachineGetApiRoleStartExecutionPolicy", + state_name_machine_arn, + partition, + region, + ) + + def _test_api_integration_with_state_machine( + self, api_id, method, path, role_name, role_arn, policy_name, state_machine_arn, partition, region + ): + apigw_client = self.client_provider.api_client + + resources = apigw_client.get_resources(restApiId=api_id)["items"] + resource = get_resource_by_path(resources, path) + + post_method = apigw_client.get_method(restApiId=api_id, resourceId=resource["id"], httpMethod=method) + method_integration = post_method["methodIntegration"] + self.assertEqual(method_integration["credentials"], role_arn) + + # checking if the uri in the API integration is set for Step Functions State Machine execution + expected_integration_uri = "arn:" + partition + ":apigateway:" + region + ":states:action/StartExecution" + self.assertEqual(method_integration["uri"], expected_integration_uri) + + # checking if the role used by the event rule to trigger the state machine execution is correct + start_execution_policy = get_policy_statements(role_name, policy_name, self.client_provider.iam_client) + self.assertEqual(len(start_execution_policy), 1, "Only one statement must be in Start Execution policy") + + start_execution_policy_statement = start_execution_policy[0] + + self.assertTrue(type(start_execution_policy_statement["Action"]) != list) + policy_action = start_execution_policy_statement["Action"] + self.assertEqual( + policy_action, + "states:StartExecution", + "Action referenced in event role policy must be 'states:StartExecution'", + ) + + self.assertTrue(type(start_execution_policy_statement["Resource"]) != list) + referenced_state_machine_arn = start_execution_policy_statement["Resource"] + self.assertEqual( + referenced_state_machine_arn, + state_machine_arn, + "State machine referenced in event role policy is incorrect", + ) + + +def get_resource_by_path(resources, path): + return next((resource for resource in resources if resource["path"] == path), None) diff --git a/integration/combination/test_state_machine_with_cwe.py b/integration/combination/test_state_machine_with_cwe.py new file mode 100644 index 0000000000..c7c39a8c96 --- /dev/null +++ b/integration/combination/test_state_machine_with_cwe.py @@ -0,0 +1,43 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements + + +class TestStateMachineWithCwe(BaseTest): + def test_state_machine_with_cwe(self): + self.create_and_verify_stack("combination/state_machine_with_cwe") + outputs = self.get_stack_outputs() + state_machine_arn = outputs["MyStateMachineArn"] + rule_name = outputs["MyEventName"] + event_role_name = outputs["MyEventRole"] + + cloud_watch_events_client = self.client_provider.cloudwatch_event_client + + # Check if the CWE rule is created with the state machine as the target + rule_name_by_target_result = cloud_watch_events_client.list_rule_names_by_target(TargetArn=state_machine_arn) + self.assertEqual(len(rule_name_by_target_result["RuleNames"]), 1) + rule_name_with_state_machine_target = rule_name_by_target_result["RuleNames"][0] + self.assertEqual(rule_name_with_state_machine_target, rule_name) + + # checking if the role used by the event rule to trigger the state machine execution is correct + start_execution_policy = get_policy_statements( + event_role_name, "MyStateMachineCWEventRoleStartExecutionPolicy", self.client_provider.iam_client + ) + self.assertEqual(len(start_execution_policy), 1, "Only one statement must be in Start Execution policy") + + start_execution_policy_statement = start_execution_policy[0] + + self.assertTrue(type(start_execution_policy_statement["Action"]) != list) + policy_action = start_execution_policy_statement["Action"] + self.assertEqual( + policy_action, + "states:StartExecution", + "Action referenced in event role policy must be 'states:StartExecution'", + ) + + self.assertTrue(type(start_execution_policy_statement["Resource"]) != list) + referenced_state_machine_arn = start_execution_policy_statement["Resource"] + self.assertEqual( + referenced_state_machine_arn, + state_machine_arn, + "State machine referenced in event role policy is incorrect", + ) diff --git a/integration/combination/test_state_machine_with_cwe_dlq_and_retry_policy.py b/integration/combination/test_state_machine_with_cwe_dlq_and_retry_policy.py new file mode 100644 index 0000000000..7b957416a2 --- /dev/null +++ b/integration/combination/test_state_machine_with_cwe_dlq_and_retry_policy.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestStateMachineWithCweDlqAndRetryPolicy(BaseTest): + def test_state_machine_with_api(self): + self.create_and_verify_stack("combination/state_machine_with_cwe_with_dlq_and_retry_policy") + outputs = self.get_stack_outputs() + state_machine_arn = outputs["MyStateMachineArn"] + rule_name = outputs["MyEventName"] + state_machine_target_dlq_arn = outputs["MyDLQArn"] + + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + + # checking if the target's DLQ and RetryPolicy properties are correct + targets = cloud_watch_event_client.list_targets_by_rule(Rule=rule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + + target = targets[0] + self.assertEqual(target["Arn"], state_machine_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], state_machine_target_dlq_arn) + + self.assertEqual(target["RetryPolicy"]["MaximumEventAgeInSeconds"], 400) + self.assertEqual(target["RetryPolicy"]["MaximumRetryAttempts"], 5) diff --git a/integration/combination/test_state_machine_with_cwe_dlq_generated.py b/integration/combination/test_state_machine_with_cwe_dlq_generated.py new file mode 100644 index 0000000000..4bea0d299a --- /dev/null +++ b/integration/combination/test_state_machine_with_cwe_dlq_generated.py @@ -0,0 +1,107 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements, get_queue_policy + + +class TestStateMachineWithCweDlqGenerated(BaseTest): + def test_state_machine_with_cwe(self): + self.create_and_verify_stack("combination/state_machine_with_cwe_dlq_generated") + outputs = self.get_stack_outputs() + state_machine_arn = outputs["MyStateMachineArn"] + rule_name = outputs["MyEventName"] + event_role_name = outputs["MyEventRole"] + state_machine_target_dlq_arn = outputs["MyDLQArn"] + state_machine_target_dlq_url = outputs["MyDLQUrl"] + + cloud_watch_events_client = self.client_provider.cloudwatch_event_client + cw_rule_result = cloud_watch_events_client.describe_rule(Name=rule_name) + + # Check if the CWE rule is created with the state machine as the target + rule_name_by_target_result = cloud_watch_events_client.list_rule_names_by_target(TargetArn=state_machine_arn) + self.assertEqual(len(rule_name_by_target_result["RuleNames"]), 1) + rule_name_with_state_machine_target = rule_name_by_target_result["RuleNames"][0] + self.assertEqual(rule_name_with_state_machine_target, rule_name) + + # checking if the role used by the event rule to trigger the state machine execution is correct + start_execution_policy = get_policy_statements( + event_role_name, "MyStateMachineCWEventRoleStartExecutionPolicy", self.client_provider.iam_client + ) + self.assertEqual(len(start_execution_policy), 1, "Only one statement must be in Start Execution policy") + + start_execution_policy_statement = start_execution_policy[0] + + self.assertTrue(type(start_execution_policy_statement["Action"]) != list) + policy_action = start_execution_policy_statement["Action"] + self.assertEqual( + policy_action, + "states:StartExecution", + "Action referenced in event role policy must be 'states:StartExecution'", + ) + + self.assertTrue(type(start_execution_policy_statement["Resource"]) != list) + referenced_state_machine_arn = start_execution_policy_statement["Resource"] + self.assertEqual( + referenced_state_machine_arn, + state_machine_arn, + "State machine referenced in event role policy is incorrect", + ) + + # checking if the target has a dead-letter queue attached to it + targets = cloud_watch_events_client.list_targets_by_rule(Rule=rule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + target = targets[0] + self.assertEqual(target["Arn"], state_machine_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], state_machine_target_dlq_arn) + + # checking target's retry policy properties + self.assertEqual(target["RetryPolicy"]["MaximumEventAgeInSeconds"], 200) + self.assertIsNone(target["RetryPolicy"].get("MaximumRetryAttempts")) + + # checking if the generated dead-letter queue has necessary resource based policy attached to it + dlq_policy = get_queue_policy(state_machine_target_dlq_url, self.client_provider.sqs_client) + self.assertEqual(len(dlq_policy), 1, "Only one statement must be in Dead-letter queue policy") + dlq_policy_statement = dlq_policy[0] + + # checking policy action + self.assertFalse( + isinstance(dlq_policy_statement["Action"], list), "Only one action must be in dead-letter queue policy" + ) # if it is an array, it means has more than one action + self.assertEqual( + dlq_policy_statement["Action"], + "sqs:SendMessage", + "Action referenced in dead-letter queue policy must be 'sqs:SendMessage'", + ) + + # checking service principal + self.assertEqual( + len(dlq_policy_statement["Principal"]), + 1, + ) + self.assertEqual( + dlq_policy_statement["Principal"]["Service"], + "events.amazonaws.com", + "Policy should grant EventBridge service principal to send messages to dead-letter queue", + ) + + # checking condition type + key, value = get_first_key_value_pair_in_dict(dlq_policy_statement["Condition"]) + self.assertEqual(key, "ArnEquals") + + # checking condition key + self.assertEqual(len(dlq_policy_statement["Condition"]), 1) + condition_kay, condition_value = get_first_key_value_pair_in_dict(value) + self.assertEqual(condition_kay, "aws:SourceArn") + + # checking condition value + self.assertEqual(len(dlq_policy_statement["Condition"][key]), 1) + self.assertEqual( + condition_value, + cw_rule_result["Arn"], + "Policy should only allow requests coming from schedule rule resource", + ) + + +def get_first_key_value_pair_in_dict(dictionary): + key = list(dictionary.keys())[0] + value = dictionary[key] + return key, value diff --git a/integration/combination/test_state_machine_with_policy_templates.py b/integration/combination/test_state_machine_with_policy_templates.py new file mode 100644 index 0000000000..f4112601da --- /dev/null +++ b/integration/combination/test_state_machine_with_policy_templates.py @@ -0,0 +1,47 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements + + +class TestStateMachineWithPolicyTemplates(BaseTest): + def test_with_policy_templates(self): + self.create_and_verify_stack("combination/state_machine_with_policy_templates") + + state_machine_role_name = self.get_stack_outputs()["MyStateMachineRole"] + + # There should be two policies created. Each policy has the name Policy + + # Verify the contents of first policy + sqs_poller_policy = get_policy_statements( + state_machine_role_name, "MyStateMachineRolePolicy0", self.client_provider.iam_client + ) + self.assertEqual(len(sqs_poller_policy), 1, "Only one statement must be in SQS Poller policy") + + sqs_policy_statement = sqs_poller_policy[0] + self.assertTrue(type(sqs_policy_statement["Resource"]) != list) + + queue_url = self.get_physical_id_by_type("AWS::SQS::Queue") + parts = queue_url.split("/") + expected_queue_name = parts[-1] + actual_queue_arn = sqs_policy_statement["Resource"] + self.assertTrue( + actual_queue_arn.endswith(expected_queue_name), + "Queue Arn " + actual_queue_arn + " must end with suffix " + expected_queue_name, + ) + + # Verify the contents of second policy + lambda_invoke_policy = get_policy_statements( + state_machine_role_name, "MyStateMachineRolePolicy1", self.client_provider.iam_client + ) + self.assertEqual(len(lambda_invoke_policy), 1, "One policies statements should be present") + + lambda_policy_statement = lambda_invoke_policy[0] + self.assertTrue(type(lambda_policy_statement["Resource"]) != list) + + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + # NOTE: The resource ARN has "*" suffix to allow for any Lambda function version as well + expected_function_suffix = "function:" + function_name + "*" + actual_function_arn = lambda_policy_statement["Resource"] + self.assertTrue( + actual_function_arn.endswith(expected_function_suffix), + "Function ARN " + actual_function_arn + " must end with suffix " + expected_function_suffix, + ) diff --git a/integration/combination/test_state_machine_with_schedule.py b/integration/combination/test_state_machine_with_schedule.py new file mode 100644 index 0000000000..8516efc0da --- /dev/null +++ b/integration/combination/test_state_machine_with_schedule.py @@ -0,0 +1,45 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements + + +class TestStateMachineWithSchedule(BaseTest): + def test_state_machine_with_schedule(self): + self.create_and_verify_stack("combination/state_machine_with_schedule") + outputs = self.get_stack_outputs() + state_machine_arn = outputs["MyStateMachineArn"] + schedule_name = outputs["MyScheduleName"] + event_role_name = outputs["MyEventRole"] + + # get the cloudwatch schedule rule + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + cw_rule_result = cloud_watch_event_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "DISABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(1 minute)") + + # checking if the role used by the event rule to trigger the state machine execution is correct + start_execution_policy = get_policy_statements( + event_role_name, "MyStateMachineCWScheduleRoleStartExecutionPolicy", self.client_provider.iam_client + ) + self.assertEqual(len(start_execution_policy), 1, "Only one statement must be in Start Execution policy") + + start_execution_policy_statement = start_execution_policy[0] + + self.assertTrue(type(start_execution_policy_statement["Action"]) != list) + policy_action = start_execution_policy_statement["Action"] + self.assertEqual( + policy_action, + "states:StartExecution", + "Action referenced in event role policy must be 'states:StartExecution'", + ) + + self.assertTrue(type(start_execution_policy_statement["Resource"]) != list) + referenced_state_machine_arn = start_execution_policy_statement["Resource"] + self.assertEqual( + referenced_state_machine_arn, + state_machine_arn, + "State machine referenced in event role policy is incorrect", + ) diff --git a/integration/combination/test_state_machine_with_schedule_dlq_and_retry_policy.py b/integration/combination/test_state_machine_with_schedule_dlq_and_retry_policy.py new file mode 100644 index 0000000000..3166ae7af0 --- /dev/null +++ b/integration/combination/test_state_machine_with_schedule_dlq_and_retry_policy.py @@ -0,0 +1,58 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_policy_statements + + +class TestStateMachineWithScheduleDlqAndRetryPolicy(BaseTest): + def test_state_machine_with_schedule(self): + self.create_and_verify_stack("combination/state_machine_with_schedule_dlq_and_retry_policy") + outputs = self.get_stack_outputs() + state_machine_arn = outputs["MyStateMachineArn"] + schedule_name = outputs["MyScheduleName"] + state_machine_target_dlq_arn = outputs["MyDLQArn"] + event_role_name = outputs["MyEventRole"] + + # get the cloudwatch schedule rule + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + cw_rule_result = cloud_watch_event_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "DISABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(1 minute)") + + # checking if the target's DLQ and RetryPolicy properties are correct + targets = cloud_watch_event_client.list_targets_by_rule(Rule=schedule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + + target = targets[0] + self.assertEqual(target["Arn"], state_machine_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], state_machine_target_dlq_arn) + + self.assertIsNone(target["RetryPolicy"].get("MaximumEventAgeInSeconds")) + self.assertEqual(target["RetryPolicy"]["MaximumRetryAttempts"], 2) + + # checking if the role used by the event rule to trigger the state machine execution is correct + start_execution_policy = get_policy_statements( + event_role_name, "MyStateMachineCWScheduleRoleStartExecutionPolicy", self.client_provider.iam_client + ) + self.assertEqual(len(start_execution_policy), 1, "Only one statement must be in Start Execution policy") + + start_execution_policy_statement = start_execution_policy[0] + + self.assertTrue(type(start_execution_policy_statement["Action"]) != list) + policy_action = start_execution_policy_statement["Action"] + self.assertEqual( + policy_action, + "states:StartExecution", + "Action referenced in event role policy must be 'states:StartExecution'", + ) + + self.assertTrue(type(start_execution_policy_statement["Resource"]) != list) + referenced_state_machine_arn = start_execution_policy_statement["Resource"] + self.assertEqual( + referenced_state_machine_arn, + state_machine_arn, + "State machine referenced in event role policy is incorrect", + ) diff --git a/integration/combination/test_state_machine_with_schedule_dlq_generated.py b/integration/combination/test_state_machine_with_schedule_dlq_generated.py new file mode 100644 index 0000000000..64918f9ec5 --- /dev/null +++ b/integration/combination/test_state_machine_with_schedule_dlq_generated.py @@ -0,0 +1,79 @@ +from integration.helpers.base_test import BaseTest +from integration.helpers.common_api import get_queue_policy + + +class TestStateMachineWithScheduleDlqGenerated(BaseTest): + def test_state_machine_with_schedule(self): + self.create_and_verify_stack("combination/state_machine_with_schedule_dlq_generated") + outputs = self.get_stack_outputs() + schedule_name = outputs["MyScheduleName"] + state_machine_arn = outputs["MyStateMachineArn"] + state_machine_target_dlq_arn = outputs["MyDLQArn"] + state_machine_target_dlq_url = outputs["MyDLQUrl"] + + # get the cloudwatch schedule rule + cloud_watch_event_client = self.client_provider.cloudwatch_event_client + cw_rule_result = cloud_watch_event_client.describe_rule(Name=schedule_name) + + # checking if the name, description and state properties are correct + self.assertEqual(cw_rule_result["Name"], schedule_name) + self.assertEqual(cw_rule_result["Description"], "test schedule") + self.assertEqual(cw_rule_result["State"], "DISABLED") + self.assertEqual(cw_rule_result["ScheduleExpression"], "rate(1 minute)") + + # checking if the target has a dead-letter queue attached to it + targets = cloud_watch_event_client.list_targets_by_rule(Rule=schedule_name)["Targets"] + + self.assertEqual(len(targets), 1, "Rule should contain a single target") + target = targets[0] + self.assertEqual(target["Arn"], state_machine_arn) + self.assertEqual(target["DeadLetterConfig"]["Arn"], state_machine_target_dlq_arn) + + # checking if the generated dead-letter queue has necessary resource based policy attached to it + dlq_policy = get_queue_policy(state_machine_target_dlq_url, self.client_provider.sqs_client) + self.assertEqual(len(dlq_policy), 1, "Only one statement must be in Dead-letter queue policy") + dlq_policy_statement = dlq_policy[0] + + # checking policy action + self.assertFalse( + isinstance(dlq_policy_statement["Action"], list), "Only one action must be in dead-letter queue policy" + ) # if it is an array, it means has more than one action + self.assertEqual( + dlq_policy_statement["Action"], + "sqs:SendMessage", + "Action referenced in dead-letter queue policy must be 'sqs:SendMessage'", + ) + + # checking service principal + self.assertEqual( + len(dlq_policy_statement["Principal"]), + 1, + ) + self.assertEqual( + dlq_policy_statement["Principal"]["Service"], + "events.amazonaws.com", + "Policy should grant EventBridge service principal to send messages to dead-letter queue", + ) + + # checking condition type + key, value = get_first_key_value_pair_in_dict(dlq_policy_statement["Condition"]) + self.assertEqual(key, "ArnEquals") + + # checking condition key + self.assertEqual(len(dlq_policy_statement["Condition"]), 1) + condition_kay, condition_value = get_first_key_value_pair_in_dict(value) + self.assertEqual(condition_kay, "aws:SourceArn") + + # checking condition value + self.assertEqual(len(dlq_policy_statement["Condition"][key]), 1) + self.assertEqual( + condition_value, + cw_rule_result["Arn"], + "Policy should only allow requests coming from schedule rule resource", + ) + + +def get_first_key_value_pair_in_dict(dictionary): + key = list(dictionary.keys())[0] + value = dictionary[key] + return key, value diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py index e12410695c..06a10a56b0 100644 --- a/integration/helpers/base_test.py +++ b/integration/helpers/base_test.py @@ -2,6 +2,8 @@ import logging import os +import requests + from integration.helpers.client_provider import ClientProvider from integration.helpers.resource import generate_suffix, create_bucket, verify_stack_resources from integration.helpers.yaml_utils import dump_yaml, load_yaml @@ -34,9 +36,9 @@ def setUpClass(cls): cls.FUNCTION_OUTPUT = "hello" cls.tests_integ_dir = Path(__file__).resolve().parents[1] cls.resources_dir = Path(cls.tests_integ_dir, "resources") - cls.template_dir = Path(cls.resources_dir, "templates", "single") + cls.template_dir = Path(cls.resources_dir, "templates") cls.output_dir = Path(cls.tests_integ_dir, "tmp") - cls.expected_dir = Path(cls.resources_dir, "expected", "single") + cls.expected_dir = Path(cls.resources_dir, "expected") cls.code_dir = Path(cls.resources_dir, "code") cls.s3_bucket_name = S3_BUCKET_PREFIX + generate_suffix() cls.session = boto3.session.Session() @@ -126,28 +128,56 @@ def tearDown(self): if os.path.exists(self.sub_input_file_path): os.remove(self.sub_input_file_path) - def create_and_verify_stack(self, file_name, parameters=None): + def create_and_verify_stack(self, file_path, parameters=None): """ Creates the Cloud Formation stack and verifies it against the expected result Parameters ---------- - file_name : string - Template file name + file_path : string + Template file name, format "folder_name/file_name" parameters : list List of parameters """ - self.output_file_path = str(Path(self.output_dir, "cfn_" + file_name + ".yaml")) - self.expected_resource_path = str(Path(self.expected_dir, file_name + ".json")) + folder, file_name = file_path.split("/") + # add a folder name before file name to avoid possible collisions between + # files in the single and combination folder + self.output_file_path = str(Path(self.output_dir, "cfn_" + folder + "_" + file_name + ".yaml")) + self.expected_resource_path = str(Path(self.expected_dir, folder, file_name + ".json")) self.stack_name = STACK_NAME_PREFIX + file_name.replace("_", "-") + "-" + generate_suffix() - self._fill_template(file_name) + self._fill_template(folder, file_name) self.transform_template() self.deploy_stack(parameters) self.verify_stack() - def update_and_verify_stack(self, file_name, parameters=None): + def update_stack(self, file_path, parameters=None): + """ + Updates the Cloud Formation stack + + Parameters + ---------- + file_path : string + Template file name, format "folder_name/file_name" + parameters : list + List of parameters + """ + if os.path.exists(self.output_file_path): + os.remove(self.output_file_path) + if os.path.exists(self.sub_input_file_path): + os.remove(self.sub_input_file_path) + + folder, file_name = file_path.split("/") + # add a folder name before file name to avoid possible collisions between + # files in the single and combination folder + self.output_file_path = str(Path(self.output_dir, "cfn_" + folder + "_" + file_name + ".yaml")) + + self._fill_template(folder, file_name) + self.transform_template() + self.deploy_stack(parameters) + + def update_and_verify_stack(self, file_path, parameters=None): """ Updates the Cloud Formation stack and verifies it against the expected result @@ -161,10 +191,14 @@ def update_and_verify_stack(self, file_name, parameters=None): """ if not self.stack_name: raise Exception("Stack not created.") - self.output_file_path = str(Path(self.output_dir, "cfn_" + file_name + ".yaml")) - self.expected_resource_path = str(Path(self.expected_dir, file_name + ".json")) - self._fill_template(file_name) + folder, file_name = file_path.split("/") + # add a folder name before file name to avoid possible collisions between + # files in the single and combination folder + self.output_file_path = str(Path(self.output_dir, "cfn_" + folder + "_" + file_name + ".yaml")) + self.expected_resource_path = str(Path(self.expected_dir, folder, file_name + ".json")) + + self._fill_template(folder, file_name) self.transform_template() self.deploy_stack(parameters) self.verify_stack(end_state="UPDATE_COMPLETE") @@ -301,17 +335,21 @@ def get_physical_id_by_logical_id(self, logical_id): return None - def _fill_template(self, file_name): + def _fill_template(self, folder, file_name): """ Replaces the template variables with their value Parameters ---------- + folder : string + The combination/single folder which contains the template file_name : string Template file name """ - input_file_path = str(Path(self.template_dir, file_name + ".yaml")) - updated_template_path = str(Path(self.output_dir, "sub_" + file_name + ".yaml")) + input_file_path = str(Path(self.template_dir, folder, file_name + ".yaml")) + # add a folder name before file name to avoid possible collisions between + # files in the single and combination folder + updated_template_path = str(Path(self.output_dir, "sub_" + folder + "_" + file_name + ".yaml")) with open(input_file_path) as f: data = f.read() for key, _ in self.code_key_to_file.items(): @@ -340,6 +378,21 @@ def set_template_resource_property(self, resource_name, property_name, value): yaml_doc["Resources"][resource_name]["Properties"][property_name] = value dump_yaml(self.sub_input_file_path, yaml_doc) + def remove_template_resource_property(self, resource_name, property_name): + """ + remove a resource property of the current SAM template + + Parameters + ---------- + resource_name: string + resource name + property_name: string + property name + """ + yaml_doc = load_yaml(self.sub_input_file_path) + del yaml_doc["Resources"][resource_name]["Properties"][property_name] + dump_yaml(self.sub_input_file_path, yaml_doc) + def get_template_resource_property(self, resource_name, property_name): yaml_doc = load_yaml(self.sub_input_file_path) return yaml_doc["Resources"][resource_name]["Properties"][property_name] @@ -375,3 +428,45 @@ def verify_stack(self, end_state="CREATE_COMPLETE"): error = verify_stack_resources(self.expected_resource_path, self.stack_resources) if error: self.fail(error) + + def verify_get_request_response(self, url, expected_status_code): + """ + Verify if the get request to a certain url return the expected status code + + Parameters + ---------- + url : string + the url for the get request + expected_status_code : string + the expected status code + """ + print("Making request to " + url) + response = requests.get(url) + self.assertEqual(response.status_code, expected_status_code, " must return HTTP " + str(expected_status_code)) + return response + + def get_default_test_template_parameters(self): + """ + get the default template parameters + """ + parameters = [ + { + "ParameterKey": "Bucket", + "ParameterValue": self.s3_bucket_name, + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "CodeKey", + "ParameterValue": "code.zip", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "SwaggerKey", + "ParameterValue": "swagger1.json", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + ] + return parameters diff --git a/integration/helpers/client_provider.py b/integration/helpers/client_provider.py index 2ffab0e19d..5686f4755f 100644 --- a/integration/helpers/client_provider.py +++ b/integration/helpers/client_provider.py @@ -1,9 +1,11 @@ import boto3 from botocore.config import Config +from threading import Lock class ClientProvider: def __init__(self): + self._lock = Lock() self._cloudformation_client = None self._s3_client = None self._api_client = None @@ -11,15 +13,26 @@ def __init__(self): self._iam_client = None self._api_v2_client = None self._sfn_client = None + self._cloudwatch_log_client = None + self._cloudwatch_event_client = None + self._sqs_client = None + self._sns_client = None + self._dynamoDB_streams_client = None + self._kinesis_client = None + self._mq_client = None + self._iot_client = None + self._kafka_client = None + self._code_deploy_client = None @property def cfn_client(self): """ Cloudformation Client """ - if not self._cloudformation_client: - config = Config(retries={"max_attempts": 10, "mode": "standard"}) - self._cloudformation_client = boto3.client("cloudformation", config=config) + with self._lock: + if not self._cloudformation_client: + config = Config(retries={"max_attempts": 10, "mode": "standard"}) + self._cloudformation_client = boto3.client("cloudformation", config=config) return self._cloudformation_client @property @@ -27,8 +40,9 @@ def s3_client(self): """ S3 Client """ - if not self._s3_client: - self._s3_client = boto3.client("s3") + with self._lock: + if not self._s3_client: + self._s3_client = boto3.client("s3") return self._s3_client @property @@ -36,8 +50,9 @@ def api_client(self): """ APIGateway Client """ - if not self._api_client: - self._api_client = boto3.client("apigateway") + with self._lock: + if not self._api_client: + self._api_client = boto3.client("apigateway") return self._api_client @property @@ -45,8 +60,9 @@ def lambda_client(self): """ Lambda Client """ - if not self._lambda_client: - self._lambda_client = boto3.client("lambda") + with self._lock: + if not self._lambda_client: + self._lambda_client = boto3.client("lambda") return self._lambda_client @property @@ -54,8 +70,9 @@ def iam_client(self): """ IAM Client """ - if not self._iam_client: - self._iam_client = boto3.client("iam") + with self._lock: + if not self._iam_client: + self._iam_client = boto3.client("iam") return self._iam_client @property @@ -63,8 +80,9 @@ def api_v2_client(self): """ APIGatewayV2 Client """ - if not self._api_v2_client: - self._api_v2_client = boto3.client("apigatewayv2") + with self._lock: + if not self._api_v2_client: + self._api_v2_client = boto3.client("apigatewayv2") return self._api_v2_client @property @@ -72,6 +90,107 @@ def sfn_client(self): """ Step Functions Client """ - if not self._sfn_client: - self._sfn_client = boto3.client("stepfunctions") + with self._lock: + if not self._sfn_client: + self._sfn_client = boto3.client("stepfunctions") return self._sfn_client + + @property + def cloudwatch_log_client(self): + """ + CloudWatch Log Client + """ + with self._lock: + if not self._cloudwatch_log_client: + self._cloudwatch_log_client = boto3.client("logs") + return self._cloudwatch_log_client + + @property + def cloudwatch_event_client(self): + """ + CloudWatch Event Client + """ + with self._lock: + if not self._cloudwatch_event_client: + self._cloudwatch_event_client = boto3.client("events") + return self._cloudwatch_event_client + + @property + def sqs_client(self): + """ + SQS Client + """ + with self._lock: + if not self._sqs_client: + self._sqs_client = boto3.client("sqs") + return self._sqs_client + + @property + def sns_client(self): + """ + SQS Client + """ + with self._lock: + if not self._sns_client: + self._sns_client = boto3.client("sns") + return self._sns_client + + @property + def dynamodb_streams_client(self): + """ + DynamoDB Stream Client + """ + with self._lock: + if not self._dynamoDB_streams_client: + self._dynamoDB_streams_client = boto3.client("dynamodbstreams") + return self._dynamoDB_streams_client + + @property + def kinesis_client(self): + """ + DynamoDB Stream Client + """ + with self._lock: + if not self._kinesis_client: + self._kinesis_client = boto3.client("kinesis") + return self._kinesis_client + + @property + def mq_client(self): + """ + MQ Client + """ + with self._lock: + if not self._mq_client: + self._mq_client = boto3.client("mq") + return self._mq_client + + @property + def iot_client(self): + """ + IOT Client + """ + with self._lock: + if not self._iot_client: + self._iot_client = boto3.client("iot") + return self._iot_client + + @property + def kafka_client(self): + """ + Kafka Client + """ + with self._lock: + if not self._kafka_client: + self._kafka_client = boto3.client("kafka") + return self._kafka_client + + @property + def code_deploy_client(self): + """ + Kafka Client + """ + with self._lock: + if not self._code_deploy_client: + self._code_deploy_client = boto3.client("codedeploy") + return self._code_deploy_client diff --git a/integration/helpers/common_api.py b/integration/helpers/common_api.py new file mode 100644 index 0000000000..c261fed5d6 --- /dev/null +++ b/integration/helpers/common_api.py @@ -0,0 +1,21 @@ +import json + + +def get_queue_policy(queue_url, sqs_client): + result = sqs_client.get_queue_attributes(QueueUrl=queue_url, AttributeNames=["Policy"]) + policy_document = result["Attributes"]["Policy"] + policy = json.loads(policy_document) + return policy["Statement"] + + +def get_function_versions(function_name, lambda_client): + versions = lambda_client.list_versions_by_function(FunctionName=function_name)["Versions"] + + # Exclude $LATEST from the list and simply return all the version numbers. + return [version["Version"] for version in versions if version["Version"] != "$LATEST"] + + +def get_policy_statements(role_name, policy_name, iam_client): + role_policy_result = iam_client.get_role_policy(RoleName=role_name, PolicyName=policy_name) + policy = role_policy_result["PolicyDocument"] + return policy["Statement"] diff --git a/integration/helpers/deployer/deployer.py b/integration/helpers/deployer/deployer.py index 4cb0de31f6..a6d6c7d884 100644 --- a/integration/helpers/deployer/deployer.py +++ b/integration/helpers/deployer/deployer.py @@ -43,7 +43,7 @@ MIN_OFFSET, ) from integration.helpers.deployer.utils.artifact_exporter import mktempfile, parse_s3_url -from integration.helpers.deployer.utils.time import utc_to_timestamp +from integration.helpers.deployer.utils.time_util import utc_to_timestamp LOG = logging.getLogger(__name__) diff --git a/integration/helpers/deployer/utils/retry.py b/integration/helpers/deployer/utils/retry.py new file mode 100644 index 0000000000..ab6b072581 --- /dev/null +++ b/integration/helpers/deployer/utils/retry.py @@ -0,0 +1,40 @@ +""" +Retry decorator to retry decorated function based on Exception with exponential backoff and number of attempts built-in. +""" +import math +import time + +from functools import wraps + + +def retry(exc, attempts=3, delay=0.05, exc_raise=Exception, exc_raise_msg=""): + """ + Retry decorator which defaults to 3 attempts based on exponential backoff + and a delay of 50ms. + After retries are exhausted, a custom Exception and Error message are raised. + + :param exc: Exception to be caught for retry + :param attempts: number of attempts before exception is allowed to be raised. + :param delay: an initial delay which will exponentially increase based on the retry attempt. + :param exc_raise: Final Exception to raise. + :param exc_raise_msg: Final message for the Exception to be raised. + :return: + """ + + def retry_wrapper(func): + @wraps(func) + def wrapper(*args, **kwargs): + remaining_attempts = attempts + retry_attempt = 1 + while remaining_attempts >= 1: + try: + return func(*args, **kwargs) + except exc: + time.sleep(math.pow(2, retry_attempt) * delay) + retry_attempt = retry_attempt + 1 + remaining_attempts = remaining_attempts - 1 + raise exc_raise(exc_raise_msg) + + return wrapper + + return retry_wrapper diff --git a/integration/helpers/deployer/utils/time.py b/integration/helpers/deployer/utils/time_util.py similarity index 100% rename from integration/helpers/deployer/utils/time.py rename to integration/helpers/deployer/utils/time_util.py diff --git a/integration/helpers/exception.py b/integration/helpers/exception.py new file mode 100644 index 0000000000..d2a87e9a7f --- /dev/null +++ b/integration/helpers/exception.py @@ -0,0 +1,7 @@ +from py.error import Error + + +class StatusCodeError(Error): + """raise when the return status code is not match the expected one""" + + pass diff --git a/integration/helpers/file_resources.py b/integration/helpers/file_resources.py index eadd533363..5f8aca2cea 100644 --- a/integration/helpers/file_resources.py +++ b/integration/helpers/file_resources.py @@ -1,9 +1,13 @@ FILE_TO_S3_URI_MAP = { "code.zip": {"type": "s3", "uri": ""}, + "code2.zip": {"type": "s3", "uri": ""}, "layer1.zip": {"type": "s3", "uri": ""}, "swagger1.json": {"type": "s3", "uri": ""}, "swagger2.json": {"type": "s3", "uri": ""}, + "binary-media.zip": {"type": "s3", "uri": ""}, "template.yaml": {"type": "http", "uri": ""}, + "MTLSCert.pem": {"type": "s3", "uri": ""}, + "MTLSCert-Updated.pem": {"type": "s3", "uri": ""}, } CODE_KEY_TO_FILE_MAP = { @@ -11,4 +15,5 @@ "contenturi": "layer1.zip", "definitionuri": "swagger1.json", "templateurl": "template.yaml", + "binaryMediaCodeUri": "binary-media.zip", } diff --git a/integration/helpers/resource.py b/integration/helpers/resource.py index 0f4632303f..d1150fc159 100644 --- a/integration/helpers/resource.py +++ b/integration/helpers/resource.py @@ -151,3 +151,23 @@ def current_region_does_not_support(services): # check if any one of the services is in the excluded services for current testing region return bool(set(services).intersection(set(region_exclude_services["regions"][region]))) + + +def first_item_in_dict(dictionary): + """ + return the first key-value pair in dictionary + + Parameters + ---------- + dictionary : Dictionary + the dictionary used to grab the first tiem + + Returns + ------- + Tuple + the first key-value pair in the dictionary + """ + if not dictionary: + return None + first_key = list(dictionary.keys())[0] + return first_key, dictionary[first_key] diff --git a/integration/resources/code/AWS_logo_RGB.png b/integration/resources/code/AWS_logo_RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..3edf631f23b05d512118e8be4b72d260830a9cf8 GIT binary patch literal 4217 zcmV-<5QguGP)}ztMht-!04snD z8DUlsdj$w@AoAD)^j7fnt$=T!(nC>ftE#(NlA-Q791@PzZgtnKzwWLE3fdJOh}oSIexk1Q`s+Cjh?F%Qt}-z>SLl(?q>6KR#QpmZyP)@)ucF4o=sr-GXU3es)B8_S+&zS1}@&kAuesG`@CQ z+Ci?Qy{s+{Bf>qs2SIwT1Ob2xj<%;9dfJlqc5aIj3~lsd&iKrOlI4giKWWoK^tr+B zD*XLtye;;m)qFY$^#NIdA^H8TgMAC&h&d5lKeY=MquZ{Vo$E1@MDNp<)tgQs#r<_}XCt&H-H3 zwLch+r5e|FdqdRn^Dgbo8P-E0oN9}nq_7eIm(-8%^llk|Sz82oE_GrK;4$QujP+`H zJs6HNa-Fz1sUt{f^0{f;3G#)>R^ks$OD62cLb0uRs2u^+u1edVeFYZ4b(2BUdbPY8 z49CX;&DR&WSzLl>Xm71HdDA>l;4? zXwy|Ne{kK$hT@O8;@o9MknnhJnRHP=!OST5jsy3D`>eK0)Hi~Jhr}J?ZeEnEs{6zt z5tFDKCEyf?2;+|h<1-Sg5D9l(8$lf65Q#>U2=ZJ}(6VDZOn~7Qi1SGNOIw`4c!RRo@JT~+=VEBU}hD83}r>QYZelV8VmdI zFc^++yXL@|Q+3q>Rl%swM9 zes*wMCdwo&TQjz@5M*tPT_dJ&*0mS!`1^>_cFgIkFk#_>o03x0K)+;Jik;(&Cdl$B1ThA~ z@e8>qA*z7%somSaW23qr!aN}LEc|;JL#@7gxK{d09;rQRdaQqOc z43aKE2E*})T!9Dd(N=;TlbnLTsy$LEseCB(YQCna)B zkrxLW;eCOtMI$8=#XB@|ouJijeJP#g4r)P4{Z5D^O~eg- z6NooyL4vtN>(z2bL^(o!Zan1KkUsJ%o6a%uXGd}zc(_$5@+}FmAa5Vx_gYz_1``3? zl0cECV8(SpFQvG5z4zAh_k#GNk>jv5_c8LaP7&Ug1hnV%0C@@2!^Q{#w}RCv@-i>6 z<6!=#*=WN5ZGa%LKyhqa52Y6C#XO(q}Xnj<2c6 zNLUw(%+*x**bKm-W=SB;TZ9-B1R2;eL7 z>L(gPni_hde16m-vokM^Z$U#SI&grA?`{ebHSPlRJ8b!l7< zP;u5jvZi;+`o@NIsp>4G3`+?ju^w5-bYTmJY96H%;CMhvgQyb+q1YR8R3ME@4nh*- zFJ)K22mK~zK~NH?90RDuAC-P&LXvXEYO#;(AQwJZ!9OWMy0jT_(!zIgg*jsO(e)H% zZ9ht@_mT3u?X&wU#y+1(pSJLw3n6)eOmE+_cxL4m5$IXi%(g{6RCdZRU zqChLCXRoRU4lFtD8OMq}YR%7JIF6LBr{;X`R3HcE9YC+jiiXyW zJ&77C^g;9N$Ef1YUI7SFb%f;Nnh1Fia^76dgZzN}u++m~IGzBwshf~ep}Aw|r*f~L zv;Dmf9b5qxkDTpyL@NQg}ne?;GSkJ>bL%n zG73dIUq}l{+^E#SvZ&n6O|`#Vty)6o9en$64Zma0hg#d)B~>mfvXE_XH>>kHA-V8z z*}l41OR)u6ua?cM+rB~eqBA2U!j^OV(2!n1}}EMB!y;$j`K=!akHQZ;6#%s$I&V&zV5Y(nnqD!|2;FMQLa7Cc+p&3|%Kh zah@bX*oyucfFHdxTpK|QT_;5!`N6v>n!o1aw0HJa1mtt2V6X>QQu3C_7wT&VGzh3Y0K>ne@poQD6lH(5x(mO-)dXFRI)!R3nA)f-6 zzTW4hczS1@EjMY{A8ZT4WBQ!}_^0&ms?7bjo>2)0M#u}INAReq(%WJ7>TYxTuMQDs z!Np+MA#0M?{v2LzH>%4BvJVq}1>OBWNm!GBb-}=r|EM*LK{L*Ftud9_^js zl;V;)6L|&T#bOjgS3zD5$2w)Zg7i%dUxQ!CeOe;Q$fm{6F>tOiPecSElFT71Ogu%N z^?IVM0tUmrSejT&5PGnv-YuyN9s&4C3yv4$2O11oamDEM9+6-%`-{YS)CTE0I(7lK zE7&q3*biHDGpfm} z^kZx0s}X{fIvG)H5;ZtqF1Rk}=(pUh89a}5QyTfFOYG4J zf>iw2s}2%nlSWJE_p0=NbeTZphaZ$k@P&SBl<({uF-q^fZE-L|zNnb)1wrwYbA2w_O7}+Wn8|S`?Xgn_Rh^4l95fp`JqdEv$xkDln&FJn^ozowRl7e_0lRk zcQfQWePfrl-o^bOWKs*OVzEm5u|=yj2TBA2JH%#4qn&SiPn*K0Xu#p>95XP!L$`J!tAYVuLFq=F-_NpeLjoz?JU-y P00000NkvXXu0mjfhg#C- literal 0 HcmV?d00001 diff --git a/integration/resources/code/MTLSCert-Updated.pem b/integration/resources/code/MTLSCert-Updated.pem new file mode 100644 index 0000000000..8d9d11b052 --- /dev/null +++ b/integration/resources/code/MTLSCert-Updated.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJjCCAw4CCQC/Rm40bTrLDjANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJV +UzEWMBQGA1UECgwNT2RkIGFuZCBFbmRzLjEXMBUGA1UECwwOQXNzb3J0ZWQgR29v +ZHMxFDASBgNVBAMMC1JPT1QtQ04uY29tMCAXDTIwMDgwNTE1MDkwM1oYDzIxMjAw +NzEyMTUwOTAzWjBUMQswCQYDVQQGEwJVUzEWMBQGA1UECgwNT2RkIGFuZCBFbmRz +LjEXMBUGA1UECwwOQXNzb3J0ZWQgR29vZHMxFDASBgNVBAMMC1JPT1QtQ04uY29t +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr0GHK0g3xIjsQXaDb3Hr +A0e/cpOUyWRoZXgftgQF6wPYK5b5QP69GXumo7t1u4L3CuSWZvF+ouARdrfnzaIH +6cL4IvDy3XMO3WlZhLOyTRd5Puk8qbBpEoGhKlg2Wn1yLrD7F6rddjKrxta0howw +zbhKj13ZQMcMS8qGARvtZa1KYLR5xqj0eMs+a74Hcrh8T1Tt9faYPj1nIlPK4npr +iV/SGV6i2H2z448qOF7GRitN5/b/yqLs6JJx48I9fBCh6u/DCk5R96JET99z1/wa +onFFQ9mXNKh95O2u8Pe4AKSoMfSzRGsJ/WWXrb3Xn8RP43ZjeM7Bk7TKpbaYR3PE +ZpdUfu+SmcGuTyBfXI+tXfMykHKHW0xb8nCXXJz8jHS2yO6Tw5MTnjCgOVvrF3SL +Q2ZpvdvjsGbk/RsG5P5RA/YCE3418ghPuijst8OL5njwodYrVOGiEkSfHk9w1DMI +AQtwKCE/nSa5+OkDF2KoCcAdyTnAn7mGF9ES/pIdvOmIUU78HW1xgw8OIAXewFDC ++Wf4ltGSWu5nUzPZM1CfNDRhwmI5hDzIuRPL4iucKJqStvzIeA0ztLW9RDoXnZ68 +KBEz7sifdI4aH0zR6ZDeKcrjmKIwHOwYifS79YbMxg6TtEX9cw3bgyBUXVPxAMu8 +NE/ckDIpKSwQIUclH2WWfNMCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAoaRtbYgs +Ie9vOc1T9h+qJTBOs6fLtpk/sFHRQfedyluNzOqF+dgDMquN1dfic88EvmK47JLW +RV5Ue5TychKjlt5nOniE0fl8aw1kxtbpeRLQ323Dmkc+5ydv7ekDg4fAHSurlE+C +obBWk2MV/jSS6Lptt8vjn68Zm9qWMEsy5WezY3dL1iJEOPPKU7zVIdUoMme1WQCE +BKGVdIWKX1zPWlsmcNpPzKqm13lpQGrP9NmkIRfhdwC/V2l52cbJ8DDsVSUtszdN +ghPkXqke9diSBDqWLsRwdq56uX45VowzCSv64XGcbtXz25NxAYZK7U9a9QEepW3L +6kyia9XVgtkXUF7RStoBXzTfdZ+kjbzld+seLtTPcVp6AD+AHJ0s6ZZDT8/QSuU4 +eyHGUBVs6y7w4tRG2rJ2qmiCvTjmcuPw6myT0LV7d9ukF2d7CsHz0O74sjl5my1I +6jwmh+mAII8ITpVBBpjLlitg89m/q6en9CPZGs21pp+ZUeloW3pnuhDm2ajmyNZq +42MLAuEyi7PMDjadjuETMlRJ4M7qxlBTF8qKwfyxHM/VzlLuBwkDfcIWvTPVC51e +VqcdEggMCJYdxzwC8D5xSyxJb5NYiI8HyfRERQSbccH1T+On9FAaT0xg7SrNYNsJ +9oYzxPKUeeMX7qLELar98YqNvULIlE2eNf4= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/integration/resources/code/MTLSCert.pem b/integration/resources/code/MTLSCert.pem new file mode 100644 index 0000000000..8d9d11b052 --- /dev/null +++ b/integration/resources/code/MTLSCert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJjCCAw4CCQC/Rm40bTrLDjANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQGEwJV +UzEWMBQGA1UECgwNT2RkIGFuZCBFbmRzLjEXMBUGA1UECwwOQXNzb3J0ZWQgR29v +ZHMxFDASBgNVBAMMC1JPT1QtQ04uY29tMCAXDTIwMDgwNTE1MDkwM1oYDzIxMjAw +NzEyMTUwOTAzWjBUMQswCQYDVQQGEwJVUzEWMBQGA1UECgwNT2RkIGFuZCBFbmRz +LjEXMBUGA1UECwwOQXNzb3J0ZWQgR29vZHMxFDASBgNVBAMMC1JPT1QtQ04uY29t +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr0GHK0g3xIjsQXaDb3Hr +A0e/cpOUyWRoZXgftgQF6wPYK5b5QP69GXumo7t1u4L3CuSWZvF+ouARdrfnzaIH +6cL4IvDy3XMO3WlZhLOyTRd5Puk8qbBpEoGhKlg2Wn1yLrD7F6rddjKrxta0howw +zbhKj13ZQMcMS8qGARvtZa1KYLR5xqj0eMs+a74Hcrh8T1Tt9faYPj1nIlPK4npr +iV/SGV6i2H2z448qOF7GRitN5/b/yqLs6JJx48I9fBCh6u/DCk5R96JET99z1/wa +onFFQ9mXNKh95O2u8Pe4AKSoMfSzRGsJ/WWXrb3Xn8RP43ZjeM7Bk7TKpbaYR3PE +ZpdUfu+SmcGuTyBfXI+tXfMykHKHW0xb8nCXXJz8jHS2yO6Tw5MTnjCgOVvrF3SL +Q2ZpvdvjsGbk/RsG5P5RA/YCE3418ghPuijst8OL5njwodYrVOGiEkSfHk9w1DMI +AQtwKCE/nSa5+OkDF2KoCcAdyTnAn7mGF9ES/pIdvOmIUU78HW1xgw8OIAXewFDC ++Wf4ltGSWu5nUzPZM1CfNDRhwmI5hDzIuRPL4iucKJqStvzIeA0ztLW9RDoXnZ68 +KBEz7sifdI4aH0zR6ZDeKcrjmKIwHOwYifS79YbMxg6TtEX9cw3bgyBUXVPxAMu8 +NE/ckDIpKSwQIUclH2WWfNMCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAoaRtbYgs +Ie9vOc1T9h+qJTBOs6fLtpk/sFHRQfedyluNzOqF+dgDMquN1dfic88EvmK47JLW +RV5Ue5TychKjlt5nOniE0fl8aw1kxtbpeRLQ323Dmkc+5ydv7ekDg4fAHSurlE+C +obBWk2MV/jSS6Lptt8vjn68Zm9qWMEsy5WezY3dL1iJEOPPKU7zVIdUoMme1WQCE +BKGVdIWKX1zPWlsmcNpPzKqm13lpQGrP9NmkIRfhdwC/V2l52cbJ8DDsVSUtszdN +ghPkXqke9diSBDqWLsRwdq56uX45VowzCSv64XGcbtXz25NxAYZK7U9a9QEepW3L +6kyia9XVgtkXUF7RStoBXzTfdZ+kjbzld+seLtTPcVp6AD+AHJ0s6ZZDT8/QSuU4 +eyHGUBVs6y7w4tRG2rJ2qmiCvTjmcuPw6myT0LV7d9ukF2d7CsHz0O74sjl5my1I +6jwmh+mAII8ITpVBBpjLlitg89m/q6en9CPZGs21pp+ZUeloW3pnuhDm2ajmyNZq +42MLAuEyi7PMDjadjuETMlRJ4M7qxlBTF8qKwfyxHM/VzlLuBwkDfcIWvTPVC51e +VqcdEggMCJYdxzwC8D5xSyxJb5NYiI8HyfRERQSbccH1T+On9FAaT0xg7SrNYNsJ +9oYzxPKUeeMX7qLELar98YqNvULIlE2eNf4= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/integration/resources/code/binary-media.zip b/integration/resources/code/binary-media.zip new file mode 100644 index 0000000000000000000000000000000000000000..3328a1bb53208964317c8da7f0c41d8e57e93bdd GIT binary patch literal 5481 zcmai&2T)Vn*2hClLJ&fN^b$fqdY9e;0Ya4`J#>&JU8;Z(K z5fo`sq>3P1>0R`N`)1ztzVF@n&Y5-goHJ*yS^xjqv(K5Iu|5SQBLGA?;qjA3fZq-f z01EK*b#)69^9nMzq6H8EjCQw4VInth-O%{0SC<5|`KvIKorq?6ylKRi}{wKp`t!IO~oqR`hesQvc&(kYx9>v0C?7`#SS^=0Z z`-s}WuoQYRx1OF*nl>en&ZD^3)CnEjv+P7-4eH&-M7!MqciOK+@%SwEmFni66^}f$ zecK-FtuB}?smQsGBQBNGzO_GPth$MWGZuI zyosg~vb)5}*y{QI+3OWyJ0x*nRZ0X?C-bDC=W8iy2Gt^(+g|}Ia>$RB=8}5`dGcXm z{X4DtV1Qk}N~945fwphKKk

VDt2jROf?VWXAeHkY;0;I*<$iNGF9c$xNC*E#&zx zGr=S?9UKhNnwQM1#LwKUA~|^$XKtQN``F$M%vVnAFWW6QOtX28TxVMFjeEN8jp-hzW`s z8S3ha=$g}ks0DGI2pHti%h~DIWRL#TDD|CJA4k&lj1+$rOP`>r|J!;Yl?nx*EzKOR z`nmf#nCfVV`TM$`NsZlv+kNLAu|AVZ77o3+kz{P7bB>1X%&Bv_Sd1xY>?1`Oh?;bL zNoZLk4Pbw)br1j`qIVW#p%K>%0RX75E(U!$Z1N8~LyIXrc+23MA6phs_qm#P)K_cB z`)HiCX=Bd=gkxw~oh+c&sX%^+?Fouqi_P7qF@6t+HBGKolsDNBioPpwz|u#3gU6q? zeXpXXrH<=x^hZ}e5oe?AjRNo<=e;lFeO)YgiUL>w2zy?66C*A3v^N(S-gH3K3whw+ z7~1wA9%h~|Ma-K$^eb)|tDz1xM~v+p?HQz(Ble}84fLArMHk!grsEko$62zdXFw%Y z*_~CO;M*X;1--zmEM;YiyVaI&D_>Bu()L~ew0?Xm7t7L~mIC;QX^~JKql9fcy^Zl#p(a zlZ|5)^|p+JLN$c&)3gnq*H1Cco)D=3?IgO2*itD*$J?pW0z^ktQJu>T9o}<&MxG2b6q5=a>tq#IO43qUO;!a^(FyfpQ zE~$s#Ob}Ws&dd_H`~ZFr0dCoS&@^d%jFZHcGOpVxP?mmhl>aWxRL4Y)=O8sVYvY@! z;qxIP-T6iO5Vf%iw|uHbScntdoaysT?3>#Sl?ZGN)0*F@7#u~l8HX)$hIyJU$ITy3 zI*|{O;l}FJSkfP}jfY;>9NFFx`(uE0OcVLQE2V(=3COBpT}>`XT& zxs;o_=M}EPk}e&X-yr{l7wfs*=s6AEPIzOnKyRqezuv1+^EAASlWe3)Z>lhgim5#~ z8~rh&u9=;-x0n3gICFzF4)5XN_f$3#jI^H06RrF)th_a%GW3aS z{`{>R7_t&^uf{EI$^sVg@yjiKP-6h>-6!|jJesLgWyjsgdPW-|q&1O}7{ zimh;sU5(n~qq)Ql|C!#K3mJ_I({9?Cn_<3r*G$wq1_Tb%DGuUJD(m$}A1 z36{bJIoXt8N%w17_hm&ZUzl{V^qKhz2d3~RC&WkDgH%seB*vHl_S9WlwJ8`${2@dO z1szc3m52`Ux8?6giP}onKZm-0{)r7k%p!kjFu9GKM;}fv-?0oKT;VJay$Ec}?8I10 zxB$KR>zv^o^4NgIEO~+C8#vMuVe~sWvB^%cr$7mOb2(47*iS4Ax|mGv)MHH`HqSUo zaJ13X+FqL2xbPhlG_{;F`e}seg$T51A=z=dRkHr2pVzNkN;?RZb%EXSM_q^pYqCoN zvm%Sna5~W%7H=(3`O2DM$Bwtef2@vqWQy#PXWM-<}rT7(Br!rzsZHByDrxiLy(8?CD4+)H+Pah$e15h+dA$Eqa#f?X z9lN+870mXkX=}PnoLQLiYzHx7>9CIl>yu1-C`PH3&cM`2!p4Vrn?q zG(kP9)bVvNTKsWCXv@La&H?_gIBW5l%z@@-ZzL;+F1Xa|+VRz@9`=WPTyw9>I~R7Q^l`Ea}xx z`mk2aDFdUI4<~5bi>~M*-r2^p%ytDUq;rabLUEB}nhmQnI<<+U&qbx>+cnZ&0Gbot z{&s;aFzCyIS7SHgt0{MMi%o$n-}~5hySfZnft)Po6V(7vFP>=k>Q_LEP08Bmwr`6K z*X_QA#h>|dXsB+wLUCnJYn(S)meW2tht$rnBOPwaFIr#i@?&7uky1_<&|u`##pE9a z4OeGB@rZyK6}q_zZ;UvO+tbtv4qR2QFkTyq&)9$L>F|UqDZa39{xoI{cq!h6KiCsT zXB;r5_Pivt|M>OIYfrw35NY+n$Qmx|zH3g9TV7m`;d5c?dU0iNV9^A7;AKRbKAi?^ zN!-n{9eB#`jy8ssaZr@?aNmx0)gT1E_NvKPM=+~eb*KrVU)8b(40nbGCMY=Xf7oMm z^HI~QILO*=`s2lk@N3mV-NO3Kd~t>hhC|Acz}RL!nV=zzSpZ_$4`e zGEHc=b^(wmBeQJmh-NyJw@N@I61PkerNoxZ92At91%qQX3C>NJO!+kSTcG}w!2{e1 zU)2p`2vUn^knX&g2idB{M^x3TxPxA0Y~ZfP&@|s(T;go7oIT(BE(I|RRKIdbDN0-kqSP#2601|!pgI+uhFxmHy^_#fTv zb*Q79zT;^4%95VgUa&H;7QCXDgwfXBqT6bU_EMMU!s%+*tmgL3uOT_rtNW90j-{z2 zu?g>@ch-B$76$FfK&HX*OAGfv;n2X(xWTv|F+ZM-bdf&sfQBdcGfj&cT~)LQnZ8W}eMxOYeYjYX8WivI!XD%lEJk!Ttu3|@#r*Sjj zQ|l`${TeFk>fBF`@~yGg=Vb-K_0+gNVYg|Ty0YB!d0j1z3AxMZDxl$n7u@U>?$qs% zD}+~bmhGx@LqToh<>?$cG9AMw^(T6HxnFY&Mgu?Aj;oqpgG&=EWwfChWy1uH7r4w~ zE1CNjRq{GhLNtzl>fT40ujqILhH*}uEb$-&w}(Xy39k11I$*hhXjjHKV)AJ~lA28K zK*Cj?!RYCSw&O~(M})3%e2%{UP1$PJqpVq3Ff;fEpJpcAMe=KxL^*^r-UVrPFZXLq z<)SpBeYzfNy9gb`8DCjratfIkY{$}@smac?WqsHQ<+&0U9;(wQerpo=CO!(lA+3o2 zX^>UwtaA>&2(+fVs6S@LbvKcE88h$Z-ZxZR+Nqi&?w54iuH3G_Xedwp-LQ_`x31ji zJzb_&AWA2i;?;IcLKzcRODG@jxy`^BHe{ zf9-QrPj8+g&$)}DM5FHsq;Xp90o14=mSWPexv4h?yF{-kdq> zE)mb)OSPSx|1q~|Sp3)59cs18)imc>+boLQLYm=Dz@M&CCDn9OrN?mB%sZH zV@v&KQOCoCsVc%EJ!Du(b2}n~cWHD=E}y56{BG;#uaCnb-19}{JSVuXLONZJ^T`iO zcQ?uhPG1(7fPvb5kViV3Wo5%tPW#_{gO01|7~DI094C0ZiwT5uibx$yZn9=h_y+B4 z2#h$gNx|zOlqe;g;=CfypfDaXoinZxlFRi+{(Q~?K@lEKHP_?JsA-9|r|VU8Yr^ju zo^yzEcoSuV?C&e-c`xZ-C3y)$$wL_65jIfPD%gv7^pu>stJUhBl>s#3S=ndNC$wB- zhq{n-<|QB-txZ7B${tsiqvT0Scf<(ws*+*|*-POGDdYsb?KPP&%Bc&C2m8XEQMJOw z#}OG9*6o8D*YZ!J)3k?fwe(r4+r@2DhVOg(dOA~>eNMhlQ6G+!))VpZUUXO=bdD{$*Q$)yo&2ASH9U7%fA2F#-O83mFIi@ya%u&dQ|yWsJZox{ctj`^{F+LlBK}L zZk8T|fx|{tF7DX~W~mESEKh+nZ*FoeNq2OjH~4VeOR>8@is}>B&SSS$r4>x~#+4}s za`*;~v!;A*%cn58faQy6!+%FJ^QL4umwIb$X)ZI|_%Sn)H3b+WmJ24|?U8aiVK&3) z5d{{;@4t$RN|aQS^v6 z)>|exRf;F*1ZOzT%2o&aaw2#_ma~wyB!9T3Vjr^W;=NqKfX`86HMt7ADDNO?eJ7r} zQ^bHJG~zO{TVOM?KyShL=Jh&BdA|pUbFb`znfL{gD(GNt+1mOm2j#mruH~sO z-Qv!{ZYD%~=WMKVNa}60_ykC3wwB%fPKDmj6bSwqXmfm^cK&US&sKu;>xrww-^_W$ zY2>&m>F#PmHGS@czGIlu_hqWgeylSKHlaOdo0?2(m?dtZ)M$~%duPA0ON|U#+YQ!$9_ig_J_2mz1Jh?$m$oIYqddLMohV z^iFEDDlP)`D0P!K2BX45nO6h~BqjQ=H7otpACP=iHL`nEes_hk_@v|vW}@ybQmo(m zDPj36i`@eHx?HuNfx-&mpmQj3Us-L5GvRi&&lNk_uEDW(v8_s2NrS!oaZCUvfEGc$ zLl?l?*UN7hAM!Fn4t+DeFLN@L9W8_Xcp+4Te3SWeeM0C5En!)0)jL?H+z!yNVt9X( zN89DTVyA?K;Wo48KbXXR`l9~a9*cs1a7U@8?EN7!_K?I90J>U6m?{lN{28wqLm0_K zLjizrQv5T^VE;RK`TwxSKLePp5Crv>gA|mCLsJ9)&>%CNLcKDnBpxgz*4~b|{h+Yo zj+Jp{cKMxbE1ap#gL?#m6``C1N^5?dERm0u1;g--MjxI6+tHKSF=b8N0QceWhtM+uJ+)CS`iw%kzznbSgd+s1uQ{o4!Xz5(NZ#g@hARNy1l2VXRL^exBklzVth= z0s&~x{5d<+iLqzHUx*3xAL94#4F3eS{>pHQHY7o?-!K>GKbY&ki};-=!T(l7{O>7# zM_&J3&%YI&L8#wb3jJF$NpisdKvJOpAgSNWI?MFCZ|r}|bPD@TnV7@>T@b$~r8)yy P04mZMKvJ^+KfC)EL6LW?YVt06K+%ff0z8G=f+N aC$U1Dgyx(8Z&o&tIz}M$1JcbP4g&y4Lq$da literal 0 HcmV?d00001 diff --git a/integration/resources/expected/combination/all_policy_templates.json b/integration/resources/expected/combination/all_policy_templates.json new file mode 100644 index 0000000000..c6006004b2 --- /dev/null +++ b/integration/resources/expected/combination/all_policy_templates.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyFunction2", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunction2Role", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyFunction3", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunction3Role", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_authorizers_invokefunction_set_none.json b/integration/resources/expected/combination/api_with_authorizers_invokefunction_set_none.json new file mode 100644 index 0000000000..48e1752b6a --- /dev/null +++ b/integration/resources/expected/combination/api_with_authorizers_invokefunction_set_none.json @@ -0,0 +1,15 @@ +[ + { "LogicalResourceId":"MyApiWithAwsIamAuthNoCallerCredentials", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiWithAwsIamAuthNoCallerCredentialsDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiWithAwsIamAuthNoCallerCredentialsProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyFunctionDefaultInvokeRole", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionDefaultInvokeRoleAPI3PermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionDefaultInvokeRoleRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyFunctionNONEInvokeRole", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionNONEInvokeRoleAPI3PermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionNONEInvokeRoleRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyFunctionWithAwsIamAuth", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionWithAwsIamAuthMyApiWithAwsIamAuthPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionWithAwsIamAuthMyApiWithNoAuthPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionWithAwsIamAuthRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_authorizers_max.json b/integration/resources/expected/combination/api_with_authorizers_max.json new file mode 100644 index 0000000000..44effbbf17 --- /dev/null +++ b/integration/resources/expected/combination/api_with_authorizers_max.json @@ -0,0 +1,21 @@ +[ + { "LogicalResourceId":"LambdaAuthInvokeRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyLambdaRequestAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiMyLambdaTokenAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyCognitoUserPool", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"MyCognitoUserPoolTwo", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"MyCognitoUserPoolClient", "ResourceType":"AWS::Cognito::UserPoolClient" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionCognitoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaRequestPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaTokenPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionIamPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaAuthFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaAuthFunctionApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaAuthFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_authorizers_max_openapi.json b/integration/resources/expected/combination/api_with_authorizers_max_openapi.json new file mode 100644 index 0000000000..7b3dee7d39 --- /dev/null +++ b/integration/resources/expected/combination/api_with_authorizers_max_openapi.json @@ -0,0 +1,25 @@ +[ + { "LogicalResourceId":"LambdaAuthInvokeRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyLambdaRequestAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiMyLambdaTokenAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyCognitoUserPool", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"MyCognitoUserPoolTwo", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"MyCognitoUserPoolClient", "ResourceType":"AWS::Cognito::UserPoolClient" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionCognitoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaRequestPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaTokenPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionIamPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaAuthFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaAuthFunctionApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionApiKeyPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaAuthFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyUsagePlanKey", "ResourceType":"AWS::ApiGateway::UsagePlanKey" }, + { "LogicalResourceId":"MyFirstApiKey", "ResourceType":"AWS::ApiGateway::ApiKey" }, + { "LogicalResourceId":"MyUsagePlan", "ResourceType":"AWS::ApiGateway::UsagePlan" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_authorizers_min.json b/integration/resources/expected/combination/api_with_authorizers_min.json new file mode 100644 index 0000000000..85cbb5b714 --- /dev/null +++ b/integration/resources/expected/combination/api_with_authorizers_min.json @@ -0,0 +1,18 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyLambdaRequestAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiMyLambdaTokenAuthAuthorizerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyCognitoUserPool", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"MyCognitoUserPoolClient", "ResourceType":"AWS::Cognito::UserPoolClient" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionCognitoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaRequestPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionLambdaTokenPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionIamPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaAuthFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaAuthFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_binary_media_types.json b/integration/resources/expected/combination/api_with_binary_media_types.json new file mode 100644 index 0000000000..bd48f03b99 --- /dev/null +++ b/integration/resources/expected/combination/api_with_binary_media_types.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body.json b/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body.json new file mode 100644 index 0000000000..bd48f03b99 --- /dev/null +++ b/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body_openapi.json b/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body_openapi.json new file mode 100644 index 0000000000..55ec077b3d --- /dev/null +++ b/integration/resources/expected/combination/api_with_binary_media_types_with_definition_body_openapi.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyLambda", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors.json b/integration/resources/expected/combination/api_with_cors.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors_only_headers.json b/integration/resources/expected/combination/api_with_cors_only_headers.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_only_headers.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors_only_max_age.json b/integration/resources/expected/combination/api_with_cors_only_max_age.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_only_max_age.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors_only_methods.json b/integration/resources/expected/combination/api_with_cors_only_methods.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_only_methods.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors_openapi.json b/integration/resources/expected/combination/api_with_cors_openapi.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_openapi.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_cors_shorthand.json b/integration/resources/expected/combination/api_with_cors_shorthand.json new file mode 100644 index 0000000000..79a2fbc3a5 --- /dev/null +++ b/integration/resources/expected/combination/api_with_cors_shorthand.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionApiOnePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionApiTwoPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_endpoint_configuration.json b/integration/resources/expected/combination/api_with_endpoint_configuration.json new file mode 100644 index 0000000000..bd48f03b99 --- /dev/null +++ b/integration/resources/expected/combination/api_with_endpoint_configuration.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_endpoint_configuration_dict.json b/integration/resources/expected/combination/api_with_endpoint_configuration_dict.json new file mode 100644 index 0000000000..bd48f03b99 --- /dev/null +++ b/integration/resources/expected/combination/api_with_endpoint_configuration_dict.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_gateway_responses.json b/integration/resources/expected/combination/api_with_gateway_responses.json new file mode 100644 index 0000000000..03b8a01d59 --- /dev/null +++ b/integration/resources/expected/combination/api_with_gateway_responses.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionIamPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_method_settings.json b/integration/resources/expected/combination/api_with_method_settings.json new file mode 100644 index 0000000000..bd48f03b99 --- /dev/null +++ b/integration/resources/expected/combination/api_with_method_settings.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_request_models.json b/integration/resources/expected/combination/api_with_request_models.json new file mode 100644 index 0000000000..6bb28b0df7 --- /dev/null +++ b/integration/resources/expected/combination/api_with_request_models.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_request_models_openapi.json b/integration/resources/expected/combination/api_with_request_models_openapi.json new file mode 100644 index 0000000000..6bb28b0df7 --- /dev/null +++ b/integration/resources/expected/combination/api_with_request_models_openapi.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionNonePermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_request_parameters_openapi.json b/integration/resources/expected/combination/api_with_request_parameters_openapi.json new file mode 100644 index 0000000000..7787fc1a18 --- /dev/null +++ b/integration/resources/expected/combination/api_with_request_parameters_openapi.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"ApiParameterFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"ApiParameterFunctionGetHtmlPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ApiParameterFunctionAnotherGetHtmlPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ApiParameterFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_resource_policies.json b/integration/resources/expected/combination/api_with_resource_policies.json new file mode 100644 index 0000000000..a92cd0eb49 --- /dev/null +++ b/integration/resources/expected/combination/api_with_resource_policies.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionApiPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionAnotherApiPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_resource_policies_aws_account.json b/integration/resources/expected/combination/api_with_resource_policies_aws_account.json new file mode 100644 index 0000000000..e99ee073a9 --- /dev/null +++ b/integration/resources/expected/combination/api_with_resource_policies_aws_account.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionApiPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_resource_refs.json b/integration/resources/expected/combination/api_with_resource_refs.json new file mode 100644 index 0000000000..4ff8679c28 --- /dev/null +++ b/integration/resources/expected/combination/api_with_resource_refs.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/api_with_usage_plan.json b/integration/resources/expected/combination/api_with_usage_plan.json new file mode 100644 index 0000000000..a04e57274e --- /dev/null +++ b/integration/resources/expected/combination/api_with_usage_plan.json @@ -0,0 +1,20 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyApiUsagePlan", "ResourceType":"AWS::ApiGateway::UsagePlan" }, + { "LogicalResourceId":"MyApiUsagePlanKey", "ResourceType":"AWS::ApiGateway::UsagePlanKey" }, + { "LogicalResourceId":"MyApiApiKey", "ResourceType":"AWS::ApiGateway::ApiKey" }, + { "LogicalResourceId":"MyApi2", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApi2Deployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi2ProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyApi3", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApi3Deployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi3ProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyApi4", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApi4Deployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi4ProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"ServerlessUsagePlan", "ResourceType":"AWS::ApiGateway::UsagePlan" }, + { "LogicalResourceId":"ServerlessUsagePlanKey", "ResourceType":"AWS::ApiGateway::UsagePlanKey" }, + { "LogicalResourceId":"ServerlessApiKey", "ResourceType":"AWS::ApiGateway::ApiKey" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/depends_on.json b/integration/resources/expected/combination/depends_on.json new file mode 100644 index 0000000000..7c4a4a0340 --- /dev/null +++ b/integration/resources/expected/combination/depends_on.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"LambdaRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"LambdaRolePolicy", "ResourceType":"AWS::IAM::Policy" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_alias.json b/integration/resources/expected/combination/function_with_alias.json new file mode 100644 index 0000000000..c87908d154 --- /dev/null +++ b/integration/resources/expected/combination/function_with_alias.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_alias_and_event_sources.json b/integration/resources/expected/combination/function_with_alias_and_event_sources.json new file mode 100644 index 0000000000..1789380262 --- /dev/null +++ b/integration/resources/expected/combination/function_with_alias_and_event_sources.json @@ -0,0 +1,27 @@ +[ + { "LogicalResourceId":"MyAwesomeFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyAwesomeFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyAwesomeFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyAwesomeFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyAwesomeFunctionDDBStream", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"MyAwesomeFunctionExplicitApiPermissionDev", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionKinesisStream", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyAwesomeFunctionCWSchedule", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"Stream", "ResourceType":"AWS::Kinesis::Stream" }, + { "LogicalResourceId":"MyAwesomeFunctionS3TriggerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ExistingRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyAwesomeFunctionNotificationTopic", "ResourceType":"AWS::SNS::Subscription" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ExistingRestApiDevStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyAwesomeFunctionCWSchedulePermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyTable", "ResourceType":"AWS::DynamoDB::Table" }, + { "LogicalResourceId":"Images", "ResourceType":"AWS::S3::Bucket" }, + { "LogicalResourceId":"ExistingRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyAwesomeFunctionCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyAwesomeFunctionCWEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionNotificationTopicPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"Notifications", "ResourceType":"AWS::SNS::Topic" }, + { "LogicalResourceId":"MyAwesomeFunctionImplicitApiPermissionProd", "ResourceType":"AWS::Lambda::Permission" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_alias_globals.json b/integration/resources/expected/combination/function_with_alias_globals.json new file mode 100644 index 0000000000..a8de059ba7 --- /dev/null +++ b/integration/resources/expected/combination/function_with_alias_globals.json @@ -0,0 +1,10 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasprod", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"FunctionWithOverride", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"FunctionWithOverrideRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"FunctionWithOverrideAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"FunctionWithOverrideVersion", "ResourceType":"AWS::Lambda::Version" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_alias_intrinsics.json b/integration/resources/expected/combination/function_with_alias_intrinsics.json new file mode 100644 index 0000000000..c87908d154 --- /dev/null +++ b/integration/resources/expected/combination/function_with_alias_intrinsics.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_all_event_types.json b/integration/resources/expected/combination/function_with_all_event_types.json new file mode 100644 index 0000000000..fc771413bf --- /dev/null +++ b/integration/resources/expected/combination/function_with_all_event_types.json @@ -0,0 +1,31 @@ +[ + { "LogicalResourceId":"FunctionOne", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"FunctionOneRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"FunctionOneImageBucketPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"Images", "ResourceType":"AWS::S3::Bucket" }, + { "LogicalResourceId":"Notifications", "ResourceType":"AWS::SNS::Topic" }, + { "LogicalResourceId":"CloudWatchLambdaLogsGroup", "ResourceType":"AWS::Logs::LogGroup" }, + { "LogicalResourceId":"MyStream", "ResourceType":"AWS::Kinesis::Stream" }, + { "LogicalResourceId":"MyDynamoDB", "ResourceType":"AWS::DynamoDB::Table" }, + { "LogicalResourceId":"MyAwesomeFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyAwesomeFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyAwesomeFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyAwesomeFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyAwesomeFunctionCWSchedule", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyAwesomeFunctionCWSchedulePermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionIoTRule", "ResourceType":"AWS::IoT::TopicRule" }, + { "LogicalResourceId":"MyAwesomeFunctionIoTRulePermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyAwesomeFunctionCWEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionS3TriggerPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionNotificationTopic", "ResourceType":"AWS::SNS::Subscription" }, + { "LogicalResourceId":"MyAwesomeFunctionNotificationTopicPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionApiEventPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyAwesomeFunctionCWLog", "ResourceType":"AWS::Logs::SubscriptionFilter" }, + { "LogicalResourceId":"MyAwesomeFunctionCWLogPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyAwesomeFunctionKinesisStream", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"MyAwesomeFunctionDDBStream", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_all_event_types_condition_false.json b/integration/resources/expected/combination/function_with_all_event_types_condition_false.json new file mode 100644 index 0000000000..7fd7c63126 --- /dev/null +++ b/integration/resources/expected/combination/function_with_all_event_types_condition_false.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"FunctionOne", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"FunctionOneRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"FunctionOneImageBucketPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"Images", "ResourceType":"AWS::S3::Bucket" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_api.json b/integration/resources/expected/combination/function_with_api.json new file mode 100644 index 0000000000..8fb2e128c1 --- /dev/null +++ b/integration/resources/expected/combination/function_with_api.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionPostApiPermissionDev", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermissionDev", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ExistingRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ExistingRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ExistingRestApiDevStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_application.json b/integration/resources/expected/combination/function_with_application.json new file mode 100644 index 0000000000..c90dda99eb --- /dev/null +++ b/integration/resources/expected/combination/function_with_application.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyNestedApp", "ResourceType":"AWS::CloudFormation::Stack" }, + { "LogicalResourceId":"MyLambdaFunctionWithApplication", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionWithApplicationRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_cloudwatch_log.json b/integration/resources/expected/combination/function_with_cloudwatch_log.json new file mode 100644 index 0000000000..96648fa985 --- /dev/null +++ b/integration/resources/expected/combination/function_with_cloudwatch_log.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionLogProcessorPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"CloudWatchLambdaLogsGroup", "ResourceType":"AWS::Logs::LogGroup" }, + { "LogicalResourceId":"MyLambdaFunctionLogProcessor", "ResourceType":"AWS::Logs::SubscriptionFilter" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_custom_code_deploy.json b/integration/resources/expected/combination/function_with_custom_code_deploy.json new file mode 100644 index 0000000000..4aa5ea974a --- /dev/null +++ b/integration/resources/expected/combination/function_with_custom_code_deploy.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"ServerlessDeploymentApplication", "ResourceType":"AWS::CodeDeploy::Application" }, + { "LogicalResourceId":"MyLambdaFunctionDeploymentGroup", "ResourceType":"AWS::CodeDeploy::DeploymentGroup" }, + { "LogicalResourceId":"DeploymentRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_cwe_dlq_and_retry_policy.json b/integration/resources/expected/combination/function_with_cwe_dlq_and_retry_policy.json new file mode 100644 index 0000000000..35a00939c0 --- /dev/null +++ b/integration/resources/expected/combination/function_with_cwe_dlq_and_retry_policy.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyLambdaFunctionCWEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyDeadLetterQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_cwe_dlq_generated.json b/integration/resources/expected/combination/function_with_cwe_dlq_generated.json new file mode 100644 index 0000000000..bc55117726 --- /dev/null +++ b/integration/resources/expected/combination/function_with_cwe_dlq_generated.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyLambdaFunctionCWEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyDlq", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MyLambdaFunctionCWEventQueuePolicy", "ResourceType":"AWS::SQS::QueuePolicy" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_deployment_alarms_and_hooks.json b/integration/resources/expected/combination/function_with_deployment_alarms_and_hooks.json new file mode 100644 index 0000000000..d5a1611727 --- /dev/null +++ b/integration/resources/expected/combination/function_with_deployment_alarms_and_hooks.json @@ -0,0 +1,16 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"PreTrafficFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"PreTrafficFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"PostTrafficFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"PostTrafficFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"ServerlessDeploymentApplication", "ResourceType":"AWS::CodeDeploy::Application" }, + { "LogicalResourceId":"MyLambdaFunctionDeploymentGroup", "ResourceType":"AWS::CodeDeploy::DeploymentGroup" }, + { "LogicalResourceId":"DeploymentRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"FunctionErrorsAlarm", "ResourceType":"AWS::CloudWatch::Alarm" }, + { "LogicalResourceId":"AliasErrorsAlarm", "ResourceType":"AWS::CloudWatch::Alarm" }, + { "LogicalResourceId":"NewVersionErrorsAlarm", "ResourceType":"AWS::CloudWatch::Alarm" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_deployment_basic.json b/integration/resources/expected/combination/function_with_deployment_basic.json new file mode 100644 index 0000000000..4aa5ea974a --- /dev/null +++ b/integration/resources/expected/combination/function_with_deployment_basic.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"ServerlessDeploymentApplication", "ResourceType":"AWS::CodeDeploy::Application" }, + { "LogicalResourceId":"MyLambdaFunctionDeploymentGroup", "ResourceType":"AWS::CodeDeploy::DeploymentGroup" }, + { "LogicalResourceId":"DeploymentRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_deployment_default_role_managed_policy.json b/integration/resources/expected/combination/function_with_deployment_default_role_managed_policy.json new file mode 100644 index 0000000000..2b061ccf41 --- /dev/null +++ b/integration/resources/expected/combination/function_with_deployment_default_role_managed_policy.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"ServerlessDeploymentApplication", "ResourceType":"AWS::CodeDeploy::Application" }, + { "LogicalResourceId":"MyLambdaFunctionDeploymentGroup", "ResourceType":"AWS::CodeDeploy::DeploymentGroup" }, + { "LogicalResourceId":"CodeDeployServiceRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_deployment_disabled.json b/integration/resources/expected/combination/function_with_deployment_disabled.json new file mode 100644 index 0000000000..77ca0a93c0 --- /dev/null +++ b/integration/resources/expected/combination/function_with_deployment_disabled.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"DeploymentRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_deployment_globals.json b/integration/resources/expected/combination/function_with_deployment_globals.json new file mode 100644 index 0000000000..4aa5ea974a --- /dev/null +++ b/integration/resources/expected/combination/function_with_deployment_globals.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"ServerlessDeploymentApplication", "ResourceType":"AWS::CodeDeploy::Application" }, + { "LogicalResourceId":"MyLambdaFunctionDeploymentGroup", "ResourceType":"AWS::CodeDeploy::DeploymentGroup" }, + { "LogicalResourceId":"DeploymentRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_dynamodb.json b/integration/resources/expected/combination/function_with_dynamodb.json new file mode 100644 index 0000000000..b76de6e5ff --- /dev/null +++ b/integration/resources/expected/combination/function_with_dynamodb.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyTable", "ResourceType":"AWS::DynamoDB::Table" }, + { "LogicalResourceId":"MyLambdaFunctionDdbStream", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_file_system_config.json b/integration/resources/expected/combination/function_with_file_system_config.json new file mode 100644 index 0000000000..5f6e3fe3da --- /dev/null +++ b/integration/resources/expected/combination/function_with_file_system_config.json @@ -0,0 +1,10 @@ +[ + { "LogicalResourceId":"EfsFileSystem", "ResourceType":"AWS::EFS::FileSystem" }, + { "LogicalResourceId":"MountTarget", "ResourceType":"AWS::EFS::MountTarget" }, + { "LogicalResourceId":"AccessPoint", "ResourceType":"AWS::EFS::AccessPoint" }, + { "LogicalResourceId":"LambdaFunctionWithEfs", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MySecurityGroup", "ResourceType":"AWS::EC2::SecurityGroup" }, + { "LogicalResourceId":"MySubnet", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"LambdaFunctionWithEfsRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_http_api.json b/integration/resources/expected/combination/function_with_http_api.json new file mode 100644 index 0000000000..7f6ef29263 --- /dev/null +++ b/integration/resources/expected/combination/function_with_http_api.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_implicit_api_and_conditions.json b/integration/resources/expected/combination/function_with_implicit_api_and_conditions.json new file mode 100644 index 0000000000..41048af23b --- /dev/null +++ b/integration/resources/expected/combination/function_with_implicit_api_and_conditions.json @@ -0,0 +1,14 @@ +[ + { "LogicalResourceId":"helloworld4", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"helloworld4Role", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"helloworld4ApiEventPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"helloworld6", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"helloworld6Role", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"helloworld6ApiEventPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"helloworld8", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"helloworld8Role", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"helloworld8ApiEventPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_implicit_http_api.json b/integration/resources/expected/combination/function_with_implicit_http_api.json new file mode 100644 index 0000000000..60b6bd9217 --- /dev/null +++ b/integration/resources/expected/combination/function_with_implicit_http_api.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessHttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"ServerlessHttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_kinesis.json b/integration/resources/expected/combination/function_with_kinesis.json new file mode 100644 index 0000000000..64511cfc40 --- /dev/null +++ b/integration/resources/expected/combination/function_with_kinesis.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStream", "ResourceType":"AWS::Kinesis::Stream" }, + { "LogicalResourceId":"MyLambdaFunctionKinesisStream", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_layer.json b/integration/resources/expected/combination/function_with_layer.json new file mode 100644 index 0000000000..05716338a1 --- /dev/null +++ b/integration/resources/expected/combination/function_with_layer.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyLambdaFunctionWithLayer", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionWithLayerRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaLayer", "ResourceType":"AWS::Lambda::LayerVersion" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_mq.json b/integration/resources/expected/combination/function_with_mq.json new file mode 100644 index 0000000000..32f9f04228 --- /dev/null +++ b/integration/resources/expected/combination/function_with_mq.json @@ -0,0 +1,15 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaExecutionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"PublicSubnetRouteTableAssociation", "ResourceType":"AWS::EC2::SubnetRouteTableAssociation" }, + { "LogicalResourceId":"AttachGateway", "ResourceType":"AWS::EC2::VPCGatewayAttachment" }, + { "LogicalResourceId":"MQSecurityGroup", "ResourceType":"AWS::EC2::SecurityGroup" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MyMqBroker", "ResourceType":"AWS::AmazonMQ::Broker" }, + { "LogicalResourceId":"PublicSubnet", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"RouteTable", "ResourceType":"AWS::EC2::RouteTable" }, + { "LogicalResourceId":"MQBrokerUserSecret", "ResourceType":"AWS::SecretsManager::Secret" }, + { "LogicalResourceId":"MyLambdaFunctionMyMqEvent", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"InternetGateway", "ResourceType":"AWS::EC2::InternetGateway" }, + { "LogicalResourceId":"Route", "ResourceType":"AWS::EC2::Route" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_mq_using_autogen_role.json b/integration/resources/expected/combination/function_with_mq_using_autogen_role.json new file mode 100644 index 0000000000..128c0787c0 --- /dev/null +++ b/integration/resources/expected/combination/function_with_mq_using_autogen_role.json @@ -0,0 +1,15 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"PublicSubnetRouteTableAssociation", "ResourceType":"AWS::EC2::SubnetRouteTableAssociation" }, + { "LogicalResourceId":"AttachGateway", "ResourceType":"AWS::EC2::VPCGatewayAttachment" }, + { "LogicalResourceId":"MQSecurityGroup", "ResourceType":"AWS::EC2::SecurityGroup" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MyMqBroker", "ResourceType":"AWS::AmazonMQ::Broker" }, + { "LogicalResourceId":"PublicSubnet", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"RouteTable", "ResourceType":"AWS::EC2::RouteTable" }, + { "LogicalResourceId":"MQBrokerUserSecret", "ResourceType":"AWS::SecretsManager::Secret" }, + { "LogicalResourceId":"MyLambdaFunctionMyMqEvent", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"InternetGateway", "ResourceType":"AWS::EC2::InternetGateway" }, + { "LogicalResourceId":"Route", "ResourceType":"AWS::EC2::Route" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_msk.json b/integration/resources/expected/combination/function_with_msk.json new file mode 100644 index 0000000000..0b96aed907 --- /dev/null +++ b/integration/resources/expected/combination/function_with_msk.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyMskStreamProcessor", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaExecutionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyMskCluster", "ResourceType":"AWS::MSK::Cluster" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MySubnetOne", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"MySubnetTwo", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"MyMskStreamProcessorMyMskEvent", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_msk_using_managed_policy.json b/integration/resources/expected/combination/function_with_msk_using_managed_policy.json new file mode 100644 index 0000000000..a257ccb249 --- /dev/null +++ b/integration/resources/expected/combination/function_with_msk_using_managed_policy.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyMskStreamProcessor", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyMskStreamProcessorRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyMskCluster", "ResourceType":"AWS::MSK::Cluster" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MySubnetOne", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"MySubnetTwo", "ResourceType":"AWS::EC2::Subnet" }, + { "LogicalResourceId":"MyMskStreamProcessorMyMskEvent", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_policy_templates.json b/integration/resources/expected/combination/function_with_policy_templates.json new file mode 100644 index 0000000000..1bd08615e2 --- /dev/null +++ b/integration/resources/expected/combination/function_with_policy_templates.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_resource_refs.json b/integration/resources/expected/combination/function_with_resource_refs.json new file mode 100644 index 0000000000..d7c8494cb2 --- /dev/null +++ b/integration/resources/expected/combination/function_with_resource_refs.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionAliasLive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyOtherFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyOtherFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_s3.json b/integration/resources/expected/combination/function_with_s3.json new file mode 100644 index 0000000000..7f07143023 --- /dev/null +++ b/integration/resources/expected/combination/function_with_s3.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionS3EventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyBucket", "ResourceType":"AWS::S3::Bucket" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_schedule.json b/integration/resources/expected/combination/function_with_schedule.json new file mode 100644 index 0000000000..86e067c17e --- /dev/null +++ b/integration/resources/expected/combination/function_with_schedule.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionRepeat", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyLambdaFunctionRepeatPermission", "ResourceType":"AWS::Lambda::Permission" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_schedule_dlq_and_retry_policy.json b/integration/resources/expected/combination/function_with_schedule_dlq_and_retry_policy.json new file mode 100644 index 0000000000..e0d7734e03 --- /dev/null +++ b/integration/resources/expected/combination/function_with_schedule_dlq_and_retry_policy.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionRepeat", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyLambdaFunctionRepeatPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyDeadLetterQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_schedule_dlq_generated.json b/integration/resources/expected/combination/function_with_schedule_dlq_generated.json new file mode 100644 index 0000000000..bd0044265d --- /dev/null +++ b/integration/resources/expected/combination/function_with_schedule_dlq_generated.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionRepeat", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyLambdaFunctionRepeatPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionRepeatQueue", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MyLambdaFunctionRepeatQueuePolicy", "ResourceType":"AWS::SQS::QueuePolicy" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_signing_profile.json b/integration/resources/expected/combination/function_with_signing_profile.json new file mode 100644 index 0000000000..028381361d --- /dev/null +++ b/integration/resources/expected/combination/function_with_signing_profile.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyUnsignedLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyUnsignedLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MySigningProfile", "ResourceType":"AWS::Signer::SigningProfile" }, + { "LogicalResourceId":"MySignedFunctionCodeSigningConfig", "ResourceType":"AWS::Lambda::CodeSigningConfig" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_sns.json b/integration/resources/expected/combination/function_with_sns.json new file mode 100644 index 0000000000..d7d5111357 --- /dev/null +++ b/integration/resources/expected/combination/function_with_sns.json @@ -0,0 +1,11 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionSNSEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MySnsTopic", "ResourceType":"AWS::SNS::Topic" }, + { "LogicalResourceId":"MyLambdaFunctionSQSSubscriptionEvent", "ResourceType":"AWS::SNS::Subscription" }, + { "LogicalResourceId":"MyLambdaFunctionSQSSubscriptionEventQueue", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MyLambdaFunctionSQSSubscriptionEventEventSourceMapping", "ResourceType":"AWS::Lambda::EventSourceMapping" }, + { "LogicalResourceId":"MyLambdaFunctionSQSSubscriptionEventQueuePolicy", "ResourceType":"AWS::SQS::QueuePolicy" }, + { "LogicalResourceId":"MyLambdaFunctionSNSEvent", "ResourceType":"AWS::SNS::Subscription" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_sqs.json b/integration/resources/expected/combination/function_with_sqs.json new file mode 100644 index 0000000000..188e4c467a --- /dev/null +++ b/integration/resources/expected/combination/function_with_sqs.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MySqsQueueFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MySqsQueueFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MySqsQueue", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MySqsQueueFunctionMySqsEvent", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/function_with_userpool_event.json b/integration/resources/expected/combination/function_with_userpool_event.json new file mode 100644 index 0000000000..f55909f48c --- /dev/null +++ b/integration/resources/expected/combination/function_with_userpool_event.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyCognitoUserPool", "ResourceType":"AWS::Cognito::UserPool" }, + { "LogicalResourceId":"PreSignupLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"PreSignupLambdaFunctionCognitoPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"PreSignupLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/http_api_with_auth.json b/integration/resources/expected/combination/http_api_with_auth.json new file mode 100644 index 0000000000..102d81d4fc --- /dev/null +++ b/integration/resources/expected/combination/http_api_with_auth.json @@ -0,0 +1,11 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyAuthFn", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyAuthFnRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionPostApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionDefaultApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/http_api_with_cors.json b/integration/resources/expected/combination/http_api_with_cors.json new file mode 100644 index 0000000000..8bbb430cd1 --- /dev/null +++ b/integration/resources/expected/combination/http_api_with_cors.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"HttpApiFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"HttpApiFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"HttpApiFunctionImplicitApiPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessHttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"ServerlessHttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/http_api_with_custom_domains_regional.json b/integration/resources/expected/combination/http_api_with_custom_domains_regional.json new file mode 100644 index 0000000000..7c7db8ba40 --- /dev/null +++ b/integration/resources/expected/combination/http_api_with_custom_domains_regional.json @@ -0,0 +1,12 @@ +[ + { "LogicalResourceId":"MyFunctionImplicitGetPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionImplicitPostPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApipostApiMapping", "ResourceType":"AWS::ApiGatewayV2::ApiMapping" }, + { "LogicalResourceId":"MyApigetApiMapping", "ResourceType":"AWS::ApiGatewayV2::ApiMapping" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"RecordSetGroupddfc299be2", "ResourceType":"AWS::Route53::RecordSetGroup" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGatewayV2::Stage" }, + { "LogicalResourceId":"ApiGatewayDomainNameV2e7a0af471b", "ResourceType":"AWS::ApiGatewayV2::DomainName" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_false.json b/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_false.json new file mode 100644 index 0000000000..a565235569 --- /dev/null +++ b/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_false.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyFunctionImplicitGetPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionImplicitPostPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGatewayV2::Stage" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_true.json b/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_true.json new file mode 100644 index 0000000000..a565235569 --- /dev/null +++ b/integration/resources/expected/combination/http_api_with_disable_execute_api_endpoint_true.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyFunctionImplicitGetPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyFunctionImplicitPostPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyApiProdStage", "ResourceType":"AWS::ApiGatewayV2::Stage" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/implicit_api_with_settings.json b/integration/resources/expected/combination/implicit_api_with_settings.json new file mode 100644 index 0000000000..0893a580e7 --- /dev/null +++ b/integration/resources/expected/combination/implicit_api_with_settings.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermissionProd", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/intrinsics_code_definition_uri.json b/integration/resources/expected/combination/intrinsics_code_definition_uri.json new file mode 100644 index 0000000000..56d4435fc8 --- /dev/null +++ b/integration/resources/expected/combination/intrinsics_code_definition_uri.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiFancyNameStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/intrinsics_serverless_api.json b/integration/resources/expected/combination/intrinsics_serverless_api.json new file mode 100644 index 0000000000..982a543355 --- /dev/null +++ b/integration/resources/expected/combination/intrinsics_serverless_api.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionPostApiPermissionStage", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionGetApiPermissionStage", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/intrinsics_serverless_function.json b/integration/resources/expected/combination/intrinsics_serverless_function.json new file mode 100644 index 0000000000..22203a11f8 --- /dev/null +++ b/integration/resources/expected/combination/intrinsics_serverless_function.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyNewRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyVpc", "ResourceType":"AWS::EC2::VPC" }, + { "LogicalResourceId":"MySecurityGroup", "ResourceType":"AWS::EC2::SecurityGroup" }, + { "LogicalResourceId":"MySubnet", "ResourceType":"AWS::EC2::Subnet" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_api.json b/integration/resources/expected/combination/state_machine_with_api.json new file mode 100644 index 0000000000..05f14d1530 --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_api.json @@ -0,0 +1,12 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"ExistingRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ExistingRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ExistingRestApiDevStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"ServerlessRestApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"ServerlessRestApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"ServerlessRestApiProdStage", "ResourceType":"AWS::ApiGateway::Stage" }, + { "LogicalResourceId":"MyStateMachinePostApiRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineGetApiRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_cwe.json b/integration/resources/expected/combination/state_machine_with_cwe.json new file mode 100644 index 0000000000..ee21302f53 --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_cwe.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWEventRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_cwe_dlq_generated.json b/integration/resources/expected/combination/state_machine_with_cwe_dlq_generated.json new file mode 100644 index 0000000000..a8b630642e --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_cwe_dlq_generated.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWEventRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWEventQueue", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MyStateMachineCWEventQueuePolicy", "ResourceType":"AWS::SQS::QueuePolicy" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_cwe_with_dlq_and_retry_policy.json b/integration/resources/expected/combination/state_machine_with_cwe_with_dlq_and_retry_policy.json new file mode 100644 index 0000000000..692dad06cd --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_cwe_with_dlq_and_retry_policy.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWEvent", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWEventRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyDeadLetterQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_policy_templates.json b/integration/resources/expected/combination/state_machine_with_policy_templates.json new file mode 100644 index 0000000000..9571f1a59d --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_policy_templates.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_schedule.json b/integration/resources/expected/combination/state_machine_with_schedule.json new file mode 100644 index 0000000000..38e01119b0 --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_schedule.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWSchedule", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWScheduleRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_schedule_dlq_and_retry_policy.json b/integration/resources/expected/combination/state_machine_with_schedule_dlq_and_retry_policy.json new file mode 100644 index 0000000000..94b05653dd --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_schedule_dlq_and_retry_policy.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWSchedule", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWScheduleRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyDeadLetterQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/combination/state_machine_with_schedule_dlq_generated.json b/integration/resources/expected/combination/state_machine_with_schedule_dlq_generated.json new file mode 100644 index 0000000000..86883f534d --- /dev/null +++ b/integration/resources/expected/combination/state_machine_with_schedule_dlq_generated.json @@ -0,0 +1,8 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine" }, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStateMachineCWSchedule", "ResourceType":"AWS::Events::Rule" }, + { "LogicalResourceId":"MyStateMachineCWScheduleRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyDlq", "ResourceType":"AWS::SQS::Queue" }, + { "LogicalResourceId":"MyStateMachineCWScheduleQueuePolicy", "ResourceType":"AWS::SQS::QueuePolicy" } +] \ No newline at end of file diff --git a/integration/resources/templates/combination/all_policy_templates.yaml b/integration/resources/templates/combination/all_policy_templates.yaml new file mode 100644 index 0000000000..0b3fa6c559 --- /dev/null +++ b/integration/resources/templates/combination/all_policy_templates.yaml @@ -0,0 +1,209 @@ +# When you add/remove a policy template, you must add it here to make sure it works. +# If you run into IAM limitations on the size inline policies inside one IAM Role, create a new function and attach +# the remaining there. + +Resources: + MyFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + Policies: + + - SQSPollerPolicy: + QueueName: name + + - LambdaInvokePolicy: + FunctionName: name + + - CloudWatchPutMetricPolicy: {} + + - EC2DescribePolicy: {} + + - DynamoDBCrudPolicy: + TableName: name + + - DynamoDBReadPolicy: + TableName: name + + - SESSendBouncePolicy: + IdentityName: name + + - ElasticsearchHttpPostPolicy: + DomainName: name + + - S3ReadPolicy: + BucketName: name + + - S3CrudPolicy: + BucketName: name + + - AMIDescribePolicy: {} + + - CloudFormationDescribeStacksPolicy: {} + + - RekognitionDetectOnlyPolicy: {} + + - RekognitionNoDataAccessPolicy: + CollectionId: id + + - RekognitionReadPolicy: + CollectionId: id + + - RekognitionWriteOnlyAccessPolicy: + CollectionId: id + + - SQSSendMessagePolicy: + QueueName: name + + - SNSPublishMessagePolicy: + TopicName: name + + - VPCAccessPolicy: {} + + - DynamoDBStreamReadPolicy: + TableName: name + StreamName: name + + - KinesisStreamReadPolicy: + StreamName: name + + - SESCrudPolicy: + IdentityName: name + + - SNSCrudPolicy: + TopicName: name + + - KinesisCrudPolicy: + StreamName: name + + - KMSDecryptPolicy: + KeyId: keyId + + - PollyFullAccessPolicy: + LexiconName: name + + - S3FullAccessPolicy: + BucketName: name + + - CodePipelineLambdaExecutionPolicy: {} + + - ServerlessRepoReadWriteAccessPolicy: {} + + - EC2CopyImagePolicy: + ImageId: id + + - CodePipelineReadOnlyPolicy: + PipelineName: pipeline + + - CloudWatchDashboardPolicy: {} + + - RekognitionFacesPolicy: {} + + - RekognitionLabelsPolicy: {} + + - DynamoDBBackupFullAccessPolicy: + TableName: table + + - DynamoDBRestoreFromBackupPolicy: + TableName: table + + - ComprehendBasicAccessPolicy: {} + + - AWSSecretsManagerGetSecretValuePolicy: + SecretArn: + Fn::Sub: arn:${AWS::Partition}:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:MyTestDatabaseSecret-a1b2c3 + + - AWSSecretsManagerRotationPolicy: + FunctionName: function + + MyFunction2: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + Policies: + + - SESEmailTemplateCrudPolicy: {} + + - SSMParameterReadPolicy: + ParameterName: name + + - MobileAnalyticsWriteOnlyAccessPolicy: {} + + - PinpointEndpointAccessPolicy: + PinpointApplicationId: id + + - FirehoseWritePolicy: + DeliveryStreamName: deliveryStream + + - FirehoseCrudPolicy: + DeliveryStreamName: deliveryStream + + - EKSDescribePolicy: {} + + - CostExplorerReadOnlyPolicy: {} + + - OrganizationsListAccountsPolicy: {} + + - DynamoDBReconfigurePolicy: + TableName: table + + - SESBulkTemplatedCrudPolicy: + IdentityName: name + + - FilterLogEventsPolicy: + LogGroupName: name + + - StepFunctionsExecutionPolicy: + StateMachineName: name + + - CodeCommitCrudPolicy: + RepositoryName: name + + - CodeCommitReadPolicy: + RepositoryName: name + + - TextractPolicy: {} + + - TextractDetectAnalyzePolicy: {} + + - TextractGetResultPolicy: {} + + - DynamoDBWritePolicy: + TableName: name + + - S3WritePolicy: + BucketName: name + + - EFSWriteAccessPolicy: + AccessPoint: name + FileSystem: name + + MyFunction3: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + Policies: + - ElasticMapReduceModifyInstanceFleetPolicy: + ClusterId: name + - ElasticMapReduceSetTerminationProtectionPolicy: + ClusterId: name + - ElasticMapReduceModifyInstanceGroupsPolicy: + ClusterId: name + - ElasticMapReduceCancelStepsPolicy: + ClusterId: name + - ElasticMapReduceTerminateJobFlowsPolicy: + ClusterId: name + - ElasticMapReduceAddJobFlowStepsPolicy: + ClusterId: name + - SageMakerCreateEndpointPolicy: + EndpointName: name + - SageMakerCreateEndpointConfigPolicy: + EndpointConfigName: name + - EcsRunTaskPolicy: + TaskDefinition: name diff --git a/integration/resources/templates/combination/api_with_authorizers_invokefunction_set_none.yaml b/integration/resources/templates/combination/api_with_authorizers_invokefunction_set_none.yaml new file mode 100644 index 0000000000..b7e9e55fca --- /dev/null +++ b/integration/resources/templates/combination/api_with_authorizers_invokefunction_set_none.yaml @@ -0,0 +1,76 @@ +Resources: + MyApiWithAwsIamAuthNoCallerCredentials: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: AWS_IAM + + MyFunctionDefaultInvokeRole: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + print("hello") + Handler: index.handler + Runtime: python3.6 + Events: + API3: + Type: Api + Properties: + RestApiId: + Ref: MyApiWithAwsIamAuthNoCallerCredentials + Method: get + Path: /MyFunctionDefaultInvokeRole + Auth: + Authorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + + MyFunctionNONEInvokeRole: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + print("hello") + Handler: index.handler + Runtime: nodejs12.x + Events: + API3: + Type: Api + Properties: + RestApiId: + Ref: MyApiWithAwsIamAuthNoCallerCredentials + Method: get + Path: /MyFunctionNONEInvokeRole + Auth: + Authorizer: AWS_IAM + InvokeRole: NONE + + MyFunctionWithAwsIamAuth: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + print("hello") + Handler: index.handler + Runtime: nodejs12.x + Events: + MyApiWithAwsIamAuth: + Type: Api + Properties: + RestApiId: + Ref: MyApiWithAwsIamAuthNoCallerCredentials + Path: /api/with-auth + Method: get + MyApiWithNoAuth: + Type: Api + Properties: + RestApiId: + Ref: MyApiWithAwsIamAuthNoCallerCredentials + Path: /api/without-auth + Method: get + Auth: + Authorizer: NONE + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApiWithAwsIamAuthNoCallerCredentials}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_authorizers_max.yaml b/integration/resources/templates/combination/api_with_authorizers_max.yaml new file mode 100644 index 0000000000..2eecfc338b --- /dev/null +++ b/integration/resources/templates/combination/api_with_authorizers_max.yaml @@ -0,0 +1,196 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyCognitoAuthorizer + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: + - Fn::GetAtt: MyCognitoUserPool.Arn + - Fn::GetAtt: MyCognitoUserPoolTwo.Arn + Identity: + Header: MyAuthorizationHeader + ValidationExpression: myauthvalidationexpression + + MyLambdaTokenAuth: + FunctionPayloadType: TOKEN + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + FunctionInvokeRole: + Fn::GetAtt: LambdaAuthInvokeRole.Arn + Identity: + Header: MyCustomAuthHeader + ValidationExpression: allow + ReauthorizeEvery: 20 + + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + FunctionInvokeRole: + Fn::GetAtt: LambdaAuthInvokeRole.Arn + Identity: + Headers: + - authorizationHeader + QueryStrings: + - authorization + - authorizationQueryString1 + ReauthorizeEvery: 0 + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + None: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /none + Auth: + Authorizer: NONE + Cognito: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /cognito + LambdaToken: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-token + Auth: + Authorizer: MyLambdaTokenAuth + LambdaRequest: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-request + Auth: + Authorizer: MyLambdaRequestAuth + Iam: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /iam + Auth: + Authorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + + MyLambdaAuthFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + const token = event.type === 'TOKEN' ? event.authorizationToken : event.queryStringParameters.authorization + const policyDocument = { + Version: '2012-10-17', + Statement: [{ + Action: 'execute-api:Invoke', + Effect: token && token.toLowerCase() === 'allow' ? 'Allow' : 'Deny', + Resource: event.methodArn + }] + } + + return { + principalId: 'user', + context: {}, + policyDocument + } + } + + LambdaAuthInvokeRole: + Type: AWS::IAM::Role + Properties: + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - apigateway.amazonaws.com + + MyLambdaAuthFunctionApiPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + Principal: apigateway.amazonaws.com + FunctionName: + Fn::GetAtt: MyLambdaAuthFunction.Arn + SourceArn: + Fn::Sub: + - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/* + - __ApiId__: + Ref: MyApi + + + MyCognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyCognitoUserPool + + MyCognitoUserPoolTwo: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyCognitoUserPoolTwo + + MyCognitoUserPoolClient: + Type: AWS::Cognito::UserPoolClient + Properties: + UserPoolId: + Ref: MyCognitoUserPool + ClientName: MyCognitoUserPoolClient + GenerateSecret: false + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' + + AuthorizerFunctionArn: + Description: "Authorizer Function Arn" + Value: + Fn::GetAtt: MyLambdaAuthFunction.Arn + + CognitoUserPoolArn: + Description: "Cognito User Pool Arn" + Value: + Fn::GetAtt: MyCognitoUserPool.Arn + + CognitoUserPoolTwoArn: + Description: "Cognito User Pool Arn" + Value: + Fn::GetAtt: MyCognitoUserPoolTwo.Arn + + LambdaAuthInvokeRoleArn: + Description: "Authorizer Function Arn" + Value: + Fn::GetAtt: LambdaAuthInvokeRole.Arn \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_authorizers_max_openapi.yaml b/integration/resources/templates/combination/api_with_authorizers_max_openapi.yaml new file mode 100644 index 0000000000..4cc07ee30d --- /dev/null +++ b/integration/resources/templates/combination/api_with_authorizers_max_openapi.yaml @@ -0,0 +1,245 @@ +Globals: + Api: + OpenApiVersion: 3.0.0 +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyCognitoAuthorizer + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: + - Fn::GetAtt: MyCognitoUserPool.Arn + - Fn::GetAtt: MyCognitoUserPoolTwo.Arn + Identity: + Header: MyAuthorizationHeader + ValidationExpression: myauthvalidationexpression + + MyLambdaTokenAuth: + FunctionPayloadType: TOKEN + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + FunctionInvokeRole: + Fn::GetAtt: LambdaAuthInvokeRole.Arn + Identity: + Header: MyCustomAuthHeader + ValidationExpression: allow + ReauthorizeEvery: 20 + + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + FunctionInvokeRole: + Fn::GetAtt: LambdaAuthInvokeRole.Arn + Identity: + Headers: + - authorizationHeader + QueryStrings: + - authorization + - authorizationQueryString1 + ReauthorizeEvery: 0 + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + None: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /none + Auth: + Authorizer: NONE + Cognito: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /cognito + LambdaToken: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-token + Auth: + Authorizer: MyLambdaTokenAuth + LambdaRequest: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-request + Auth: + Authorizer: MyLambdaRequestAuth + Iam: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /iam + Auth: + Authorizer: AWS_IAM + InvokeRole: CALLER_CREDENTIALS + ApiKey: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /apikey + Auth: + Authorizer: NONE + ApiKeyRequired: true + + MyLambdaAuthFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + const token = event.type === 'TOKEN' ? event.authorizationToken : event.queryStringParameters.authorization + const policyDocument = { + Version: '2012-10-17', + Statement: [{ + Action: 'execute-api:Invoke', + Effect: token && token.toLowerCase() === 'allow' ? 'Allow' : 'Deny', + Resource: event.methodArn + }] + } + + return { + principalId: 'user', + context: {}, + policyDocument + } + } + + LambdaAuthInvokeRole: + Type: AWS::IAM::Role + Properties: + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaRole + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - apigateway.amazonaws.com + + MyLambdaAuthFunctionApiPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + Principal: apigateway.amazonaws.com + FunctionName: + Fn::GetAtt: MyLambdaAuthFunction.Arn + SourceArn: + Fn::Sub: + - arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/* + - __ApiId__: + Ref: MyApi + + MyCognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyCognitoUserPool + + MyCognitoUserPoolTwo: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyCognitoUserPoolTwo + + MyCognitoUserPoolClient: + Type: AWS::Cognito::UserPoolClient + Properties: + UserPoolId: + Ref: MyCognitoUserPool + ClientName: MyCognitoUserPoolClient + GenerateSecret: false + + MyFirstApiKey: + Type: AWS::ApiGateway::ApiKey + DependsOn: + - MyUsagePlan + Properties: + Enabled: true + StageKeys: + - RestApiId: + Ref: MyApi + StageName: + Ref: MyApi.Stage + + MyUsagePlan: + Type: AWS::ApiGateway::UsagePlan + DependsOn: + - MyApi + Properties: + ApiStages: + - ApiId: + Ref: MyApi + Stage: + Ref: MyApi.Stage + + MyUsagePlanKey: + Type: AWS::ApiGateway::UsagePlanKey + Properties: + KeyId: + Ref: MyFirstApiKey + UsagePlanId: + Ref: MyUsagePlan + KeyType: API_KEY + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' + + AuthorizerFunctionArn: + Description: "Authorizer Function Arn" + Value: + Fn::GetAtt: MyLambdaAuthFunction.Arn + + CognitoUserPoolArn: + Description: "Cognito User Pool Arn" + Value: + Fn::GetAtt: MyCognitoUserPool.Arn + + CognitoUserPoolTwoArn: + Description: "Cognito User Pool Arn" + Value: + Fn::GetAtt: MyCognitoUserPoolTwo.Arn + + LambdaAuthInvokeRoleArn: + Description: "Authorizer Function Arn" + Value: + Fn::GetAtt: LambdaAuthInvokeRole.Arn + + ApiKeyId: + Description: "ID of the API Key" + Value: + Ref: MyFirstApiKey diff --git a/integration/resources/templates/combination/api_with_authorizers_min.yaml b/integration/resources/templates/combination/api_with_authorizers_min.yaml new file mode 100644 index 0000000000..558a9df2e5 --- /dev/null +++ b/integration/resources/templates/combination/api_with_authorizers_min.yaml @@ -0,0 +1,130 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: + Fn::GetAtt: MyCognitoUserPool.Arn + MyLambdaTokenAuth: + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: + Fn::GetAtt: MyLambdaAuthFunction.Arn + Identity: + QueryStrings: + - authorization + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + None: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /none + Cognito: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /cognito + Auth: + Authorizer: MyCognitoAuthorizer + LambdaToken: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-token + Auth: + Authorizer: MyLambdaTokenAuth + LambdaRequest: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /lambda-request + Auth: + Authorizer: MyLambdaRequestAuth + Iam: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /iam + Auth: + Authorizer: AWS_IAM + + MyLambdaAuthFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + const token = event.type === 'TOKEN' ? event.authorizationToken : event.queryStringParameters.authorization + const policyDocument = { + Version: '2012-10-17', + Statement: [{ + Action: 'execute-api:Invoke', + Effect: token && token.toLowerCase() === 'allow' ? 'Allow' : 'Deny', + Resource: event.methodArn + }] + } + + return { + principalId: 'user', + context: {}, + policyDocument + } + } + + MyCognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyCognitoUserPool + + MyCognitoUserPoolClient: + Type: AWS::Cognito::UserPoolClient + Properties: + UserPoolId: + Ref: MyCognitoUserPool + ClientName: MyCognitoUserPoolClient + GenerateSecret: false + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' + + AuthorizerFunctionArn: + Description: "Authorizer Function Arn" + Value: + Fn::GetAtt: MyLambdaAuthFunction.Arn + + CognitoUserPoolArn: + Description: "Cognito User Pool Arn" + Value: + Fn::GetAtt: MyCognitoUserPool.Arn \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_binary_media_types.yaml b/integration/resources/templates/combination/api_with_binary_media_types.yaml new file mode 100644 index 0000000000..6041961330 --- /dev/null +++ b/integration/resources/templates/combination/api_with_binary_media_types.yaml @@ -0,0 +1,22 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + ImageType: + Type: String + Default: image~1gif + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: ${definitionuri} + BinaryMediaTypes: + - image~1jpg + - {"Fn::Join": ["~1", ["image", "png"]]} + - {"Ref": "ImageType"} + diff --git a/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body.yaml b/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body.yaml new file mode 100644 index 0000000000..60597624b1 --- /dev/null +++ b/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body.yaml @@ -0,0 +1,46 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + ImageType: + Type: String + Default: image~1gif +Globals: + Api: + # Send/receive binary data through the APIs + BinaryMediaTypes: + # These are equivalent to image/gif and image/png when deployed + - image~1jpg + - image~1gif + - image~1png + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionBody: + # Simple HTTP Proxy API + swagger: "2.0" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" diff --git a/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body_openapi.yaml b/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body_openapi.yaml new file mode 100644 index 0000000000..c4a361d7f6 --- /dev/null +++ b/integration/resources/templates/combination/api_with_binary_media_types_with_definition_body_openapi.yaml @@ -0,0 +1,77 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + BinaryMediaCodeKey: + Type: String + SwaggerKey: + Type: String + ImageType: + Type: String + Default: image~1gif +Globals: + Api: + # Send/receive binary data through the APIs + BinaryMediaTypes: + # These are equivalent to image/gif and image/png when deployed + - image~1jpg + - image~1gif + - image~1png + - application~1octet-stream + OpenApiVersion: 3.0.1 + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionBody: + # Simple HTTP Proxy API + openapi: "3.0.1" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api" + basePath: "/none" + schemes: + - "https" + paths: + "/none": + get: + x-amazon-apigateway-integration: + httpMethod: POST + type: aws_proxy + contentHandling: CONVERT_TO_BINARY + passthroughBehavior: NEVER + uri: + Fn::Sub: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambda.Arn}/invocations + responses: {} + + MyLambda: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: + Bucket: + Ref: Bucket + Key: + Ref: BinaryMediaCodeKey + Events: + None: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /none + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' + MyLambda: + Description: MyLambda Function ARN + Value: + Fn::GetAtt: MyLambda.Arn diff --git a/integration/resources/templates/combination/api_with_cors.yaml b/integration/resources/templates/combination/api_with_cors.yaml new file mode 100644 index 0000000000..19b853550d --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors.yaml @@ -0,0 +1,46 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: + AllowMethods: "'methods'" + AllowHeaders: "'headers'" + AllowOrigin: "'origins'" + MaxAge: "'600'" + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_cors_only_headers.yaml b/integration/resources/templates/combination/api_with_cors_only_headers.yaml new file mode 100644 index 0000000000..a84c94b7d6 --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_only_headers.yaml @@ -0,0 +1,48 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Parameters: + CorsParam: + Type: String + Default: "headers" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: + AllowHeaders: {"Fn::Sub": "'${CorsParam}'"} + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_cors_only_max_age.yaml b/integration/resources/templates/combination/api_with_cors_only_max_age.yaml new file mode 100644 index 0000000000..0b5ccc97a3 --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_only_max_age.yaml @@ -0,0 +1,48 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Parameters: + CorsParam: + Type: String + Default: "600" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: + MaxAge: {"Fn::Sub": "'${CorsParam}'"} + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_cors_only_methods.yaml b/integration/resources/templates/combination/api_with_cors_only_methods.yaml new file mode 100644 index 0000000000..38f54a33d9 --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_only_methods.yaml @@ -0,0 +1,48 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Parameters: + CorsParam: + Type: String + Default: "methods" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: + AllowMethods: {"Fn::Sub": "'${CorsParam}'"} + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_cors_openapi.yaml b/integration/resources/templates/combination/api_with_cors_openapi.yaml new file mode 100644 index 0000000000..19b853550d --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_openapi.yaml @@ -0,0 +1,46 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: + AllowMethods: "'methods'" + AllowHeaders: "'headers'" + AllowOrigin: "'origins'" + MaxAge: "'600'" + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_cors_shorthand.yaml b/integration/resources/templates/combination/api_with_cors_shorthand.yaml new file mode 100644 index 0000000000..d44733539a --- /dev/null +++ b/integration/resources/templates/combination/api_with_cors_shorthand.yaml @@ -0,0 +1,47 @@ +Conditions: + IsChina: + Fn::Or: + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-north-1" + - Fn::Equals: + - Ref: "AWS::Region" + - "cn-northwest-1" + +Parameters: + OriginValue: + Type: String + Default: "origins" + +Globals: + Api: + EndpointConfiguration: REGIONAL + Cors: {"Fn::Sub": "'${OriginValue}'"} + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + ApiOne: + Type: Api + Properties: + Path: /apione + Method: any + + ApiTwo: + Type: Api + Properties: + Path: /apitwo + Method: post + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_endpoint_configuration.yaml b/integration/resources/templates/combination/api_with_endpoint_configuration.yaml new file mode 100644 index 0000000000..a4e5cb0ac1 --- /dev/null +++ b/integration/resources/templates/combination/api_with_endpoint_configuration.yaml @@ -0,0 +1,21 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + Config: + Type: String + Default: REGIONAL + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + EndpointConfiguration: { + "Ref": "Config" + } + DefinitionUri: ${definitionuri} + diff --git a/integration/resources/templates/combination/api_with_endpoint_configuration_dict.yaml b/integration/resources/templates/combination/api_with_endpoint_configuration_dict.yaml new file mode 100644 index 0000000000..4866fa80ea --- /dev/null +++ b/integration/resources/templates/combination/api_with_endpoint_configuration_dict.yaml @@ -0,0 +1,17 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + EndpointConfiguration: + Type: REGIONAL + DefinitionUri: ${definitionuri} + diff --git a/integration/resources/templates/combination/api_with_gateway_responses.yaml b/integration/resources/templates/combination/api_with_gateway_responses.yaml new file mode 100644 index 0000000000..7b52013ab5 --- /dev/null +++ b/integration/resources/templates/combination/api_with_gateway_responses.yaml @@ -0,0 +1,39 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + GatewayResponses: + DEFAULT_4XX: + ResponseParameters: + Headers: + Access-Control-Allow-Origin: "'*'" + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + Iam: + Type: Api + Properties: + RestApiId: + Ref: MyApi + Method: get + Path: /iam + Auth: + Authorizer: AWS_IAM + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_method_settings.yaml b/integration/resources/templates/combination/api_with_method_settings.yaml new file mode 100644 index 0000000000..53e0e8b11b --- /dev/null +++ b/integration/resources/templates/combination/api_with_method_settings.yaml @@ -0,0 +1,13 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: ${definitionuri} + MethodSettings: [{ + "LoggingLevel": "INFO", + "MetricsEnabled": True, + "DataTraceEnabled": True, + "ResourcePath": "/*", + "HttpMethod": "*" + }] diff --git a/integration/resources/templates/combination/api_with_request_models.yaml b/integration/resources/templates/combination/api_with_request_models.yaml new file mode 100644 index 0000000000..19f08e109c --- /dev/null +++ b/integration/resources/templates/combination/api_with_request_models.yaml @@ -0,0 +1,41 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Models: + User: + type: object + properties: + username: + type: string + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + None: + Type: Api + Properties: + RequestModel: + Model: User + Required: true + RestApiId: + Ref: MyApi + Method: get + Path: /none + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_request_models_openapi.yaml b/integration/resources/templates/combination/api_with_request_models_openapi.yaml new file mode 100644 index 0000000000..d3449ba8ea --- /dev/null +++ b/integration/resources/templates/combination/api_with_request_models_openapi.yaml @@ -0,0 +1,42 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + OpenApiVersion: 3.0.1 + StageName: Prod + Models: + User: + type: object + properties: + username: + type: string + + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + InlineCode: | + exports.handler = async (event, context, callback) => { + return { + statusCode: 200, + body: 'Success' + } + } + Events: + None: + Type: Api + Properties: + RequestModel: + Model: User + Required: true + RestApiId: + Ref: MyApi + Method: get + Path: /none + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: 'https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_request_parameters_openapi.yaml b/integration/resources/templates/combination/api_with_request_parameters_openapi.yaml new file mode 100644 index 0000000000..025e2965fd --- /dev/null +++ b/integration/resources/templates/combination/api_with_request_parameters_openapi.yaml @@ -0,0 +1,47 @@ +Globals: + Api: + OpenApiVersion: '3.0.1' + CacheClusterEnabled: true + CacheClusterSize: '0.5' + MethodSettings: + - ResourcePath: /one + HttpMethod: "GET" + CachingEnabled: true + CacheTtlInSeconds: 15 +Resources: + ApiParameterFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = function(event, context, callback) { + var returnVal = "undef"; + if (event.queryStringParameters.type === "time") { + returnVal = "time" + Date.now(); + } + + if (event.queryStringParameters.type === "date") { + returnVal = "Random" + Math.random(); + } + + callback(null, { + "statusCode": 200, + "body": returnVal + }); + } + Handler: index.handler + Runtime: nodejs12.x + Events: + GetHtml: + Type: Api + Properties: + Path: /one + Method: get + RequestParameters: + - method.request.querystring.type: + Required: true + Caching: true + AnotherGetHtml: + Type: Api + Properties: + Path: /two + Method: get diff --git a/integration/resources/templates/combination/api_with_resource_policies.yaml b/integration/resources/templates/combination/api_with_resource_policies.yaml new file mode 100644 index 0000000000..f7d58c3bb8 --- /dev/null +++ b/integration/resources/templates/combination/api_with_resource_policies.yaml @@ -0,0 +1,62 @@ +Conditions: + IsChina: + Fn::Equals: + - Ref: "AWS::Partition" + - "aws-cn" + +Globals: + Api: + OpenApiVersion: "3.0.1" + Auth: + ResourcePolicy: + CustomStatements: [{ + "Effect": "Allow", + "Principal": "*", + "Action": "execute-api:Invoke", + "Resource": "execute-api:*/*/*" + }] + SourceVpcWhitelist: ['vpc-1234'] + SourceVpcBlacklist: ['vpce-5678'] + IpRangeWhitelist: ['1.2.3.4'] + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + Api: + Type: Api + Properties: + Path: /apione + Method: any + AnotherApi: + Type: Api + Properties: + Path: /apitwo + Method: get + +Outputs: + Region: + Description: "Region" + Value: + Ref: AWS::Region + + AccountId: + Description: "Account Id" + Value: + Ref: AWS::AccountId + + Partition: + Description: "Partition" + Value: + Ref: AWS::Partition + + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessRestApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_resource_policies_aws_account.yaml b/integration/resources/templates/combination/api_with_resource_policies_aws_account.yaml new file mode 100644 index 0000000000..d08014b5aa --- /dev/null +++ b/integration/resources/templates/combination/api_with_resource_policies_aws_account.yaml @@ -0,0 +1,33 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Events: + Api: + Type: Api + Properties: + Auth: + ResourcePolicy: + AwsAccountWhitelist: + - Ref: 'AWS::AccountId' + Method: get + Path: /get + +Outputs: + Region: + Description: "Region" + Value: + Ref: AWS::Region + + AccountId: + Description: "Account Id" + Value: + Ref: AWS::AccountId + + Partition: + Description: "Partition" + Value: + Ref: AWS::Partition \ No newline at end of file diff --git a/integration/resources/templates/combination/api_with_resource_refs.yaml b/integration/resources/templates/combination/api_with_resource_refs.yaml new file mode 100644 index 0000000000..6d539f0052 --- /dev/null +++ b/integration/resources/templates/combination/api_with_resource_refs.yaml @@ -0,0 +1,22 @@ +# Test to verify that resource references available on the Api resource are properly resolved + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: ${definitionuri} + +Outputs: + StageName: + Value: + Ref: MyApi.Stage + + ApiId: + Value: + Ref: MyApi + + DeploymentId: + Value: + Ref: MyApi.Deployment + diff --git a/integration/resources/templates/combination/api_with_usage_plan.yaml b/integration/resources/templates/combination/api_with_usage_plan.yaml new file mode 100644 index 0000000000..6a2f1ca074 --- /dev/null +++ b/integration/resources/templates/combination/api_with_usage_plan.yaml @@ -0,0 +1,155 @@ +Parameters: + UsagePlanType: + Type: String + Default: PER_API +Globals: + Api: + OpenApiVersion: "2.0" + Auth: + ApiKeyRequired: True + UsagePlan: + CreateUsagePlan: + Ref: UsagePlanType + Description: My test usage plan + Quota: + Limit: 500 + Period: MONTH + Throttle: + BurstLimit: 100 + RateLimit: 50 + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionBody: + # Simple HTTP Proxy API + swagger: "2.0" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api 1" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" + + MyApi2: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + OpenApiVersion: 3.0.1 + Auth: + UsagePlan: + CreateUsagePlan: SHARED + DefinitionBody: + # Simple HTTP Proxy API + openapi: "3.0.1" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api 2" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" + + MyApi3: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + OpenApiVersion: 3.0.1 + Auth: + UsagePlan: + CreateUsagePlan: NONE + DefinitionBody: + # Simple HTTP Proxy API + openapi: "3.0.1" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api 3" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" + + MyApi4: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + OpenApiVersion: 3.0.1 + Auth: + UsagePlan: + CreateUsagePlan: SHARED + DefinitionBody: + # Simple HTTP Proxy API + openapi: "3.0.1" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api 4" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" + +Outputs: + MyApiUsagePlan: + Value: + Ref: MyApiUsagePlan + MyApiApiKey: + Value: + Ref: MyApiApiKey + ServerlessUsagePlan: + Value: + Ref: ServerlessUsagePlan + ServerlessApiKey: + Value: + Ref: ServerlessApiKey + \ No newline at end of file diff --git a/integration/resources/templates/combination/depends_on.yaml b/integration/resources/templates/combination/depends_on.yaml new file mode 100644 index 0000000000..2aeb01c188 --- /dev/null +++ b/integration/resources/templates/combination/depends_on.yaml @@ -0,0 +1,51 @@ +Resources: + + # Classic DependsOn case + # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html#gatewayattachment + # Template would fail to create stack without DependsOn + # https://github.com/awslabs/serverless-application-model/issues/68#issuecomment-276495326 + + MyLambdaFunction: + Type: "AWS::Serverless::Function" + DependsOn: LambdaRolePolicy + Properties: + Role: + "Fn::GetAtt": LambdaRole.Arn + Handler: lambda_function.lambda_handler + Runtime: python2.7 + Timeout: 15 + CodeUri: ${codeuri} + + LambdaRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: + - "sts:AssumeRole" + Principal: + Service: + - "lambda.amazonaws.com" + + LambdaRolePolicy: + Type: "AWS::IAM::Policy" + Properties: + PolicyName: "LambdaRolePolicy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: + - "logs:CreateLogGroup" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + Resource: + - "*" + Roles: + - + Ref: "LambdaRole" + diff --git a/integration/resources/templates/combination/function_with_alias.yaml b/integration/resources/templates/combination/function_with_alias.yaml new file mode 100644 index 0000000000..876ae03d28 --- /dev/null +++ b/integration/resources/templates/combination/function_with_alias.yaml @@ -0,0 +1,8 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + AutoPublishAlias: Live diff --git a/integration/resources/templates/combination/function_with_alias_and_event_sources.yaml b/integration/resources/templates/combination/function_with_alias_and_event_sources.yaml new file mode 100644 index 0000000000..196d0196b7 --- /dev/null +++ b/integration/resources/templates/combination/function_with_alias_and_event_sources.yaml @@ -0,0 +1,106 @@ +# Testing Alias Invoke with ALL event sources supported by Lambda +# We are looking to check if the event sources and their associated Lambda::Permission resources are +# connect to the Alias and *not* the function + +Resources: + MyAwesomeFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: index.handler + Runtime: nodejs12.x + + AutoPublishAlias: Live + + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + + ExplicitApi: + Type: Api + Properties: + Path: /pathget + Method: get + RestApiId: + Ref: ExistingRestApi + + ImplicitApi: + Type: Api + Properties: + Path: /add + Method: post + + S3Trigger: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: s3:ObjectCreated:* + + NotificationTopic: + Type: SNS + Properties: + Topic: + Ref: Notifications + + KinesisStream: + Type: Kinesis + Properties: + Stream: + Fn::GetAtt: ["Stream", "Arn"] + BatchSize: 100 + StartingPosition: TRIM_HORIZON + + DDBStream: + Type: DynamoDB + Properties: + Stream: + Fn::GetAtt: ["MyTable", "StreamArn"] + BatchSize: 200 + StartingPosition: LATEST + + Notifications: + Type: AWS::SNS::Topic + + Images: + Type: AWS::S3::Bucket + + ExistingRestApi: + Type: AWS::Serverless::Api + Properties: + StageName: "Dev" + DefinitionUri: ${definitionuri} + + Stream: + Type: AWS::Kinesis::Stream + Properties: + ShardCount: 1 + + # What an irony the I can't use AWS::Serverless::SimpleTable here because it doesn't support streams specification + MyTable: + Type: AWS::DynamoDB::Table + Properties: + # Enable DDB streams + StreamSpecification: + StreamViewType: KEYS_ONLY + + ProvisionedThroughput: + WriteCapacityUnits: 5 + ReadCapacityUnits: 5 + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - KeyType: HASH + AttributeName: id + diff --git a/integration/resources/templates/combination/function_with_alias_globals.yaml b/integration/resources/templates/combination/function_with_alias_globals.yaml new file mode 100644 index 0000000000..ff282423bd --- /dev/null +++ b/integration/resources/templates/combination/function_with_alias_globals.yaml @@ -0,0 +1,21 @@ +Globals: + Function: + AutoPublishAlias: prod + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + + # Alias is inherited from globals here + + FunctionWithOverride: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + AutoPublishAlias: Live diff --git a/integration/resources/templates/combination/function_with_alias_intrinsics.yaml b/integration/resources/templates/combination/function_with_alias_intrinsics.yaml new file mode 100644 index 0000000000..2cd0e4f950 --- /dev/null +++ b/integration/resources/templates/combination/function_with_alias_intrinsics.yaml @@ -0,0 +1,29 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + AliasName: + Type: String + Default: Live + +Globals: + Function: + AutoPublishAlias: + Ref: AliasName + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: + # Just trying to create a complex intrinsic function where only a part of it can be resolved + Bucket: + Fn::Join: ["", [{Ref: Bucket}]] + Key: + # Even though the entire Sub won't be resolved, translator will substitute ${Key} with value passed at runtime + Fn::Sub: "${CodeKey}" diff --git a/integration/resources/templates/combination/function_with_all_event_types.yaml b/integration/resources/templates/combination/function_with_all_event_types.yaml new file mode 100644 index 0000000000..5f96c3fb54 --- /dev/null +++ b/integration/resources/templates/combination/function_with_all_event_types.yaml @@ -0,0 +1,157 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + MyCondition: + Fn::Equals: + - true + - true + +Resources: + +# S3 Event without condition, using same bucket + FunctionOne: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async () => ‘Hello World!' + Handler: index.handler + Runtime: nodejs12.x + Events: + ImageBucket: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: s3:ObjectRemoved:* + +# All Event Types + MyAwesomeFunction: + Type: 'AWS::Serverless::Function' + Condition: MyCondition + Properties: + InlineCode: | + exports.handler = async () => ‘Hello World!' + Handler: index.handler + Runtime: nodejs12.x + AutoPublishAlias: Live + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: + Fn::Sub: + - TestSchedule${__StackName__} + - __StackName__: + Fn::Select: + - 3 + - Fn::Split: + - "-" + - Ref: AWS::StackName + Description: test schedule + Enabled: False + + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + + CWLog: + Type: CloudWatchLogs + Properties: + LogGroupName: + Ref: CloudWatchLambdaLogsGroup + FilterPattern: My pattern + + IoTRule: + Type: IoTRule + Properties: + Sql: SELECT * FROM 'topic/test' + AwsIotSqlVersion: beta + + S3Trigger: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: s3:ObjectCreated:* + + NotificationTopic: + Type: SNS + Properties: + Topic: + Ref: Notifications + + KinesisStream: + Type: Kinesis + Properties: + Stream: + Fn::GetAtt: [MyStream, Arn] + BatchSize: 100 + MaximumBatchingWindowInSeconds: 20 + StartingPosition: TRIM_HORIZON + + DDBStream: + Type: DynamoDB + Properties: + Stream: + Fn::GetAtt: [MyDynamoDB, StreamArn] + BatchSize: 200 + MaximumBatchingWindowInSeconds: 20 + StartingPosition: LATEST + + ApiEvent: + Type: Api + Properties: + Path: /hello + Method: get + + Notifications: + Condition: MyCondition + Type: AWS::SNS::Topic + + Images: + Type: AWS::S3::Bucket + + CloudWatchLambdaLogsGroup: + Type: AWS::Logs::LogGroup + Condition: MyCondition + Properties: + RetentionInDays: 7 + + MyStream: + Type: AWS::Kinesis::Stream + Condition: MyCondition + Properties: + ShardCount: 1 + + MyDynamoDB: + Type: 'AWS::DynamoDB::Table' + Condition: MyCondition + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 5 + WriteCapacityUnits: 5 + StreamSpecification: + StreamViewType: NEW_IMAGE + +Outputs: + ScheduleName: + Description: "Name of the cw schedule" + Value: + Fn::Sub: + - TestSchedule${__StackName__} + - __StackName__: + Fn::Select: + - 3 + - Fn::Split: + - "-" + - Ref: AWS::StackName \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_all_event_types_condition_false.yaml b/integration/resources/templates/combination/function_with_all_event_types_condition_false.yaml new file mode 100644 index 0000000000..31594ddd5a --- /dev/null +++ b/integration/resources/templates/combination/function_with_all_event_types_condition_false.yaml @@ -0,0 +1,131 @@ +AWSTemplateFormatVersion: '2010-09-09' +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + +# S3 Event without condition, using same bucket + FunctionOne: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async () => ‘Hello World!' + Handler: index.handler + Runtime: nodejs12.x + Events: + ImageBucket: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: s3:ObjectRemoved:* + +# All Event Types + MyAwesomeFunction: + Type: 'AWS::Serverless::Function' + Condition: MyCondition + Properties: + InlineCode: | + exports.handler = async () => ‘Hello World!' + Handler: index.handler + Runtime: nodejs12.x + AutoPublishAlias: Live + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + + CWLog: + Type: CloudWatchLogs + Properties: + LogGroupName: + Ref: CloudWatchLambdaLogsGroup + FilterPattern: My pattern + + IoTRule: + Type: IoTRule + Properties: + Sql: SELECT * FROM 'topic/test' + AwsIotSqlVersion: beta + + S3Trigger: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: s3:ObjectCreated:* + + NotificationTopic: + Type: SNS + Properties: + Topic: + Ref: Notifications + + KinesisStream: + Type: Kinesis + Properties: + Stream: + Fn::GetAtt: [MyStream, Arn] + BatchSize: 100 + StartingPosition: TRIM_HORIZON + + DDBStream: + Type: DynamoDB + Properties: + Stream: + Fn::GetAtt: [MyDynamoDB, StreamArn] + BatchSize: 200 + StartingPosition: LATEST + + ApiEvent: + Type: Api + Properties: + Path: /hello + Method: get + + Notifications: + Condition: MyCondition + Type: AWS::SNS::Topic + + Images: + Type: AWS::S3::Bucket + + CloudWatchLambdaLogsGroup: + Type: AWS::Logs::LogGroup + Condition: MyCondition + Properties: + RetentionInDays: 7 + + MyStream: + Type: AWS::Kinesis::Stream + Condition: MyCondition + Properties: + ShardCount: 1 + + MyDynamoDB: + Type: 'AWS::DynamoDB::Table' + Condition: MyCondition + Properties: + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 5 + WriteCapacityUnits: 5 + StreamSpecification: + StreamViewType: NEW_IMAGE \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_api.yaml b/integration/resources/templates/combination/function_with_api.yaml new file mode 100644 index 0000000000..0c8966b67d --- /dev/null +++ b/integration/resources/templates/combination/function_with_api.yaml @@ -0,0 +1,33 @@ +Resources: + + # Create one API resource. This will be referred to by the Lambda function + ExistingRestApi: + Type: AWS::Serverless::Api + Properties: + StageName: "Dev" + DefinitionUri: ${definitionuri} + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + GetApi: + Type: Api + Properties: + Path: /pathget + Method: get + RestApiId: + Ref: ExistingRestApi + + PostApi: + Type: Api + Properties: + Path: /pathpost + Method: post + RestApiId: + Ref: ExistingRestApi diff --git a/integration/resources/templates/combination/function_with_application.yaml b/integration/resources/templates/combination/function_with_application.yaml new file mode 100644 index 0000000000..99573baa07 --- /dev/null +++ b/integration/resources/templates/combination/function_with_application.yaml @@ -0,0 +1,33 @@ +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunctionWithApplication: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Environment: + Variables: + TABLE_NAME: + Fn::GetAtt: ["MyNestedApp", "Outputs.TableName"] + + MyNestedApp: + Type: AWS::Serverless::Application + Condition: TrueCondition + Properties: + Location: ${templateurl} + + MyNestedAppFalseCondition: + Type: AWS::Serverless::Application + Condition: FalseCondition + Properties: + Location: ${templateurl} \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_cloudwatch_log.yaml b/integration/resources/templates/combination/function_with_cloudwatch_log.yaml new file mode 100644 index 0000000000..66ce01d8a9 --- /dev/null +++ b/integration/resources/templates/combination/function_with_cloudwatch_log.yaml @@ -0,0 +1,20 @@ +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Events: + LogProcessor: + Type: CloudWatchLogs + Properties: + LogGroupName: + Ref: CloudWatchLambdaLogsGroup + FilterPattern: My filter pattern + + CloudWatchLambdaLogsGroup: + Type: AWS::Logs::LogGroup + Properties: + RetentionInDays: 7 \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_custom_code_deploy.yaml b/integration/resources/templates/combination/function_with_custom_code_deploy.yaml new file mode 100644 index 0000000000..73b3dfcfc0 --- /dev/null +++ b/integration/resources/templates/combination/function_with_custom_code_deploy.yaml @@ -0,0 +1,45 @@ +# Just one function with a deployment preference +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: index.handler + Runtime: python2.7 + + AutoPublishAlias: Live + + DeploymentPreference: + Type: CustomLambdaDeploymentConfiguration + Role: + Fn::GetAtt: [ DeploymentRole, Arn ] + + DeploymentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Resource: "*" + Action: + - "cloudwatch:DescribeAlarms" + - "lambda:UpdateAlias" + - "lambda:GetAlias" + - "lambda:InvokeFunction" + - "s3:Get*" + - "sns:Publish" diff --git a/integration/resources/templates/combination/function_with_cwe_dlq_and_retry_policy.yaml b/integration/resources/templates/combination/function_with_cwe_dlq_and_retry_policy.yaml new file mode 100644 index 0000000000..d97d9ad159 --- /dev/null +++ b/integration/resources/templates/combination/function_with_cwe_dlq_and_retry_policy.yaml @@ -0,0 +1,46 @@ +Resources: + MyDeadLetterQueue: + Type: AWS::SQS::Queue + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 6 + MaximumEventAgeInSeconds: 900 + DeadLetterConfig: + Arn: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" + +Outputs: + MyLambdaArn: + Description: "Arn of the Lambda target" + Value: + Fn::GetAtt: + - "MyLambdaFunction" + - "Arn" + MyEventName: + Description: "Name of the CloudWatchEvent rule created" + Value: + Ref: MyLambdaFunctionCWEvent + MyDLQArn: + Description: "Arn of the dead-letter queue provided for the CWE rule target" + Value: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" diff --git a/integration/resources/templates/combination/function_with_cwe_dlq_generated.yaml b/integration/resources/templates/combination/function_with_cwe_dlq_generated.yaml new file mode 100644 index 0000000000..40a6c546fa --- /dev/null +++ b/integration/resources/templates/combination/function_with_cwe_dlq_generated.yaml @@ -0,0 +1,46 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 6 + MaximumEventAgeInSeconds: 900 + DeadLetterConfig: + Type: SQS + QueueLogicalId: MyDlq + +Outputs: + MyLambdaArn: + Description: "Arn of the Lambda target" + Value: + Fn::GetAtt: + - "MyLambdaFunction" + - "Arn" + MyEventName: + Description: "Name of the CWE rule created" + Value: + Ref: MyLambdaFunctionCWEvent + MyDLQArn: + Description: "Arn of the dead-letter queue provided for the CWE rule target" + Value: + Fn::GetAtt: + - "MyDlq" + - "Arn" + MyDLQUrl: + Description: "Url of the dead-letter queue provided for the CWE rule target" + Value: + Ref: MyDlq + diff --git a/integration/resources/templates/combination/function_with_deployment_alarms_and_hooks.yaml b/integration/resources/templates/combination/function_with_deployment_alarms_and_hooks.yaml new file mode 100644 index 0000000000..7bd7c6d6ec --- /dev/null +++ b/integration/resources/templates/combination/function_with_deployment_alarms_and_hooks.yaml @@ -0,0 +1,128 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + + AutoPublishAlias: Live + + DeploymentPreference: + Role: + Fn::GetAtt: [ DeploymentRole, Arn ] + Type: Canary10Percent5Minutes + Alarms: + - {"Ref": "NewVersionErrorsAlarm"} + - {"Ref": "AliasErrorsAlarm"} + - {"Ref": "FunctionErrorsAlarm"} +# Hooks: + # These hooks just hang so we're commenting them out for now or the deployment waits on them forever +# PreTraffic: {"Ref": "PreTrafficFunction"} +# PostTraffic: {"Ref": "PostTrafficFunction"} + + DeploymentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Resource: "*" + Action: + - "cloudwatch:DescribeAlarms" + - "lambda:UpdateAlias" + - "lambda:GetAlias" + - "lambda:InvokeFunction" + - "s3:Get*" + - "sns:Publish" + + FunctionErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + Namespace: AWS/Lambda + MetricName: Error + + Dimensions: + - Name: FunctionName + Value: + "Fn::GetAtt": ["MyLambdaFunction", "Arn"] + + Statistic: Maximum + Period: 60 + EvaluationPeriods: 5 + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: 1.0 + + AliasErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + Namespace: AWS/Lambda + MetricName: Error + + Dimensions: + - Name: FunctionName + Value: + "Fn::GetAtt": ["MyLambdaFunction", "Arn"] + - Name: Alias + Value: Live + + Statistic: Maximum + Period: 60 + EvaluationPeriods: 5 + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: 1.0 + + # Alarm pointing to the Errors metric on "latest" executed function version + # When the deployment is happening, this alarm will point to the new version that ie being deployed + NewVersionErrorsAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + Namespace: AWS/Lambda + MetricName: Error + + Dimensions: + - Name: FunctionName + Value: + "Fn::GetAtt": ["MyLambdaFunction", "Arn"] + + - Name: Alias + Value: Live + + - Name: ExecutedVersion + Value: + "Fn::GetAtt": ["MyLambdaFunction.Version", "Version"] + + Statistic: Maximum + Period: 60 + EvaluationPeriods: 5 + ComparisonOperator: GreaterThanOrEqualToThreshold + Threshold: 1.0 + + PreTrafficFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + + PostTrafficFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_deployment_basic.yaml b/integration/resources/templates/combination/function_with_deployment_basic.yaml new file mode 100644 index 0000000000..1d40b00878 --- /dev/null +++ b/integration/resources/templates/combination/function_with_deployment_basic.yaml @@ -0,0 +1,45 @@ +# Just one function with a deployment preference +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: index.handler + Runtime: python2.7 + + AutoPublishAlias: Live + + DeploymentPreference: + Type: AllAtOnce + Role: + Fn::GetAtt: [ DeploymentRole, Arn ] + + DeploymentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Resource: "*" + Action: + - "cloudwatch:DescribeAlarms" + - "lambda:UpdateAlias" + - "lambda:GetAlias" + - "lambda:InvokeFunction" + - "s3:Get*" + - "sns:Publish" diff --git a/integration/resources/templates/combination/function_with_deployment_default_role_managed_policy.yaml b/integration/resources/templates/combination/function_with_deployment_default_role_managed_policy.yaml new file mode 100644 index 0000000000..1a627a6a3b --- /dev/null +++ b/integration/resources/templates/combination/function_with_deployment_default_role_managed_policy.yaml @@ -0,0 +1,10 @@ +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: index.handler + Runtime: python2.7 + AutoPublishAlias: Live + DeploymentPreference: + Type: Canary10Percent5Minutes diff --git a/integration/resources/templates/combination/function_with_deployment_disabled.yaml b/integration/resources/templates/combination/function_with_deployment_disabled.yaml new file mode 100644 index 0000000000..977f94f696 --- /dev/null +++ b/integration/resources/templates/combination/function_with_deployment_disabled.yaml @@ -0,0 +1,59 @@ +Parameters: + # The test harness passes theses parameters even though they aren't used. So specify them here + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + Enabled: + Type: String + Default: False + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + + AutoPublishAlias: Live + + DeploymentPreference: + Type: AllAtOnce + Role: + Fn::GetAtt: [ DeploymentRole, Arn ] + + Enabled: + Ref: Enabled + + DeploymentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Resource: "*" + Action: + - "cloudwatch:DescribeAlarms" + - "lambda:UpdateAlias" + - "lambda:GetAlias" + - "lambda:InvokeFunction" + - "s3:Get*" + - "sns:Publish" diff --git a/integration/resources/templates/combination/function_with_deployment_globals.yaml b/integration/resources/templates/combination/function_with_deployment_globals.yaml new file mode 100644 index 0000000000..99b280a027 --- /dev/null +++ b/integration/resources/templates/combination/function_with_deployment_globals.yaml @@ -0,0 +1,50 @@ +Parameters: + TypeParam: + Default: AllAtOnce + Type: String +Globals: + Function: + DeploymentPreference: + Type: + Ref: TypeParam + Role: + Fn::GetAtt: [ DeploymentRole, Arn ] + +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: index.handler + Runtime: python2.7 + AutoPublishAlias: Live + + DeploymentRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - codedeploy.amazonaws.com + + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Resource: "*" + Action: + - "cloudwatch:DescribeAlarms" + - "lambda:UpdateAlias" + - "lambda:GetAlias" + - "lambda:InvokeFunction" + - "s3:Get*" + - "sns:Publish" diff --git a/integration/resources/templates/combination/function_with_dynamodb.yaml b/integration/resources/templates/combination/function_with_dynamodb.yaml new file mode 100644 index 0000000000..10dd31bd80 --- /dev/null +++ b/integration/resources/templates/combination/function_with_dynamodb.yaml @@ -0,0 +1,42 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + DdbStream: + Type: DynamoDB + Properties: + Stream: + # Connect with the table we have created in this template + Fn::GetAtt: [MyTable, StreamArn] + + BatchSize: 10 + StartingPosition: TRIM_HORIZON + TumblingWindowInSeconds: 120 + FunctionResponseTypes: + - ReportBatchItemFailures + + MyTable: + Type: AWS::DynamoDB::Table + Properties: + + AttributeDefinitions: + - { AttributeName : id, AttributeType : S } + + KeySchema: + - { "AttributeName" : "id", "KeyType" : "HASH"} + + ProvisionedThroughput: + ReadCapacityUnits: "5" + WriteCapacityUnits: "5" + + StreamSpecification: + StreamViewType: "NEW_IMAGE" + + + diff --git a/integration/resources/templates/combination/function_with_file_system_config.yaml b/integration/resources/templates/combination/function_with_file_system_config.yaml new file mode 100644 index 0000000000..c1d2578627 --- /dev/null +++ b/integration/resources/templates/combination/function_with_file_system_config.yaml @@ -0,0 +1,71 @@ +Description: SAM + Lambda + EFS + +Resources: + EfsFileSystem: + Type: AWS::EFS::FileSystem + + MountTarget: + Type: AWS::EFS::MountTarget + Properties: + FileSystemId: + Ref: EfsFileSystem + SubnetId: + Ref: MySubnet + SecurityGroups: + - + Fn::GetAtt: MySecurityGroup.GroupId + + AccessPoint: + Type: AWS::EFS::AccessPoint + Properties: + FileSystemId: + Ref: EfsFileSystem + + LambdaFunctionWithEfs: + Type: AWS::Serverless::Function + DependsOn: MountTarget + Properties: + InlineCode: | + const fs = require('fs') + const path = require('path') + const efsMountPath = '/mnt/efs' + + exports.handler = async (event, context, callback) => { + const directory = path.join(efsMountPath, event.body) + const files = fs.readdirSync(directory) + return files + } + Handler: index.handler + MemorySize: 128 + Runtime: nodejs12.x + Timeout: 3 + VpcConfig: + SecurityGroupIds: + - + Fn::GetAtt: MySecurityGroup.GroupId + SubnetIds: + - + Ref: MySubnet + FileSystemConfigs: + - Arn: + Fn::GetAtt: AccessPoint.Arn + LocalMountPath: /mnt/EFS + + MyVpc: + Type: "AWS::EC2::VPC" + Properties: + CidrBlock: "10.0.0.0/16" + + MySecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: "my test group" + VpcId: + Ref: MyVpc + + MySubnet: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.0.0/24" diff --git a/integration/resources/templates/combination/function_with_http_api.yaml b/integration/resources/templates/combination/function_with_http_api.yaml new file mode 100644 index 0000000000..f275e2969d --- /dev/null +++ b/integration/resources/templates/combination/function_with_http_api.yaml @@ -0,0 +1,37 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: python3.7 + InlineCode: | + def handler(event, context): + return {'body': 'Hello World!', 'statusCode': 200} + MemorySize: 128 + Events: + GetApi: + Type: HttpApi + Properties: + ApiId: + Ref: MyApi + Method: GET + Path: /some/path + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionBody: + info: + version: '1.0' + title: + Ref: AWS::StackName + paths: + "/some/path": {} + "/other": {} + openapi: 3.0.1 + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: "https://${MyApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/" \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_implicit_api_and_conditions.yaml b/integration/resources/templates/combination/function_with_implicit_api_and_conditions.yaml new file mode 100644 index 0000000000..2d0a33a01b --- /dev/null +++ b/integration/resources/templates/combination/function_with_implicit_api_and_conditions.yaml @@ -0,0 +1,225 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: A template to test for implicit API condition handling. +Conditions: + MyCondition: + Fn::Equals: + - true + - false + Cond: + Fn::Equals: + - true + - false + Cond1: + Fn::Equals: + - true + - false + Cond2: + Fn::Equals: + - true + - false + Cond3: + Fn::Equals: + - true + - false + Cond4: + Fn::Equals: + - true + - true + Cond5: + Fn::Equals: + - true + - false + Cond6: + Fn::Equals: + - true + - true + Cond7: + Fn::Equals: + - true + - false + Cond8: + Fn::Equals: + - true + - true + Cond9: + Fn::Equals: + - true + - false + +Resources: + hello: + Type: 'AWS::Serverless::Function' + Condition: MyCondition + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub + Method: get + helloworld: + Type: 'AWS::Serverless::Function' + Condition: Cond + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub + Method: post + helloworld1: + Type: 'AWS::Serverless::Function' + Condition: Cond1 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub1 + Method: post + helloworld2: + Type: 'AWS::Serverless::Function' + Condition: Cond2 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub2 + Method: post + helloworld3: + Type: 'AWS::Serverless::Function' + Condition: Cond3 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub3 + Method: post + helloworld4: + Type: 'AWS::Serverless::Function' + Condition: Cond4 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub4 + Method: post + helloworld5: + Type: 'AWS::Serverless::Function' + Condition: Cond5 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub5 + Method: post + helloworld6: + Type: 'AWS::Serverless::Function' + Condition: Cond6 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub6 + Method: post + helloworld7: + Type: 'AWS::Serverless::Function' + Condition: Cond7 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub7 + Method: post + helloworld8: + Type: 'AWS::Serverless::Function' + Condition: Cond8 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub8 + Method: post + helloworld9: + Type: 'AWS::Serverless::Function' + Condition: Cond9 + Properties: + Handler: index.handler + Runtime: nodejs12.x + MemorySize: 128 + Timeout: 3 + InlineCode: | + exports.handler = async () => ‘Hello World!' + Events: + ApiEvent: + Type: Api + Properties: + Path: /sub9 + Method: post \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_implicit_http_api.yaml b/integration/resources/templates/combination/function_with_implicit_http_api.yaml new file mode 100644 index 0000000000..4b585a3c83 --- /dev/null +++ b/integration/resources/templates/combination/function_with_implicit_http_api.yaml @@ -0,0 +1,20 @@ +Resources: + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: python3.7 + InlineCode: | + def handler(event, context): + return {'body': 'Hello World!', 'statusCode': 200} + MemorySize: 128 + Events: + GetApi: + Type: HttpApi + +Outputs: + ApiUrl: + Description: "API endpoint URL for Prod environment" + Value: + Fn::Sub: "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/" \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_kinesis.yaml b/integration/resources/templates/combination/function_with_kinesis.yaml new file mode 100644 index 0000000000..97d44003c0 --- /dev/null +++ b/integration/resources/templates/combination/function_with_kinesis.yaml @@ -0,0 +1,27 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + KinesisStream: + Type: Kinesis + Properties: + Stream: + # Connect with the stream we have created in this template + Fn::GetAtt: [MyStream, Arn] + + BatchSize: 100 + StartingPosition: LATEST + TumblingWindowInSeconds: 120 + FunctionResponseTypes: + - ReportBatchItemFailures + + MyStream: + Type: AWS::Kinesis::Stream + Properties: + ShardCount: 1 diff --git a/integration/resources/templates/combination/function_with_layer.yaml b/integration/resources/templates/combination/function_with_layer.yaml new file mode 100644 index 0000000000..f58a29017d --- /dev/null +++ b/integration/resources/templates/combination/function_with_layer.yaml @@ -0,0 +1,39 @@ +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunctionWithLayer: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Layers: + - + Ref: MyLambdaLayer + + MyLambdaLayer: + Type: AWS::Serverless::LayerVersion + Condition: TrueCondition + Properties: + ContentUri: ${contenturi} + RetentionPolicy: Delete + + MyLambdaLayerFalseCondition: + Type: AWS::Serverless::LayerVersion + Condition: FalseCondition + Properties: + ContentUri: ${contenturi} + RetentionPolicy: Delete + +Outputs: + MyLambdaLayerArn: + Value: + Ref: MyLambdaLayer \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_mq.yaml b/integration/resources/templates/combination/function_with_mq.yaml new file mode 100644 index 0000000000..703b6c6965 --- /dev/null +++ b/integration/resources/templates/combination/function_with_mq.yaml @@ -0,0 +1,188 @@ +Parameters: + MQBrokerUser: + Description: The user to access the Amazon MQ broker. + Type: String + Default: testBrokerUser + MinLength: 2 + ConstraintDescription: The Amazon MQ broker user is required ! + MQBrokerPassword: + Description: The password to access the Amazon MQ broker. Min 12 characters + Type: String + Default: testBrokerPassword + MinLength: 12 + ConstraintDescription: The Amazon MQ broker password is required ! + NoEcho: true + +Resources: + MyVpc: + Type: AWS::EC2::VPC + Properties: + CidrBlock: "10.42.0.0/16" + DependsOn: + - MyLambdaExecutionRole + + InternetGateway: + Type: AWS::EC2::InternetGateway + + AttachGateway: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: + Ref: MyVpc + InternetGatewayId: + Ref: InternetGateway + RouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: MyVpc + + Route: + Type: AWS::EC2::Route + DependsOn: AttachGateway + Properties: + RouteTableId: + Ref: RouteTable + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: + Ref: InternetGateway + PublicSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.42.0.0/24" + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + + PublicSubnetRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PublicSubnet + RouteTableId: + Ref: RouteTable + + MQSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Limits security group ingress and egress traffic for the Amazon + MQ instance + VpcId: + Ref: MyVpc + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 8162 + ToPort: 8162 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 61617 + ToPort: 61617 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 5671 + ToPort: 5671 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 61614 + ToPort: 61614 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 8883 + ToPort: 8883 + CidrIp: '0.0.0.0/0' + + MyLambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: ['sts:AssumeRole'] + Effect: Allow + Principal: + Service: [lambda.amazonaws.com] + Policies: + - PolicyName: IntegrationTestExecution + PolicyDocument: + Statement: + - Action: [ 'ec2:CreateNetworkInterface', + 'ec2:CreateNetworkInterfacePermission', + 'ec2:DeleteNetworkInterface', + 'ec2:DeleteNetworkInterfacePermission', + 'ec2:DetachNetworkInterface', + 'ec2:DescribeSubnets', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DescribeVpcs', + 'ec2:DescribeInternetGateways', + 'ec2:DescribeNetworkInterfacePermissions', + 'ec2:DescribeSecurityGroups', + 'ec2:DescribeRouteTables', + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents', + 'kms:Decrypt', + 'mq:DescribeBroker', + 'secretsmanager:GetSecretValue'] + Effect: Allow + Resource: '*' + ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'] + Tags: + - {Value: SAM, Key: 'lambda:createdBy'} + + MyMqBroker: + Properties: + BrokerName: TestMQBroker + DeploymentMode: SINGLE_INSTANCE + EngineType: ACTIVEMQ + EngineVersion: 5.15.12 + HostInstanceType: mq.t3.micro + Logs: + Audit: true + General: true + PubliclyAccessible: true + AutoMinorVersionUpgrade: false + SecurityGroups: + - Ref: MQSecurityGroup + SubnetIds: + - Ref: PublicSubnet + Users: + - ConsoleAccess: true + Groups: + - admin + Username: + Ref: MQBrokerUser + Password: + Ref: MQBrokerPassword + Type: AWS::AmazonMQ::Broker + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs12.x + Handler: index.handler + CodeUri: ${codeuri} + Role: + Fn::GetAtt: [ MyLambdaExecutionRole, Arn ] + Events: + MyMqEvent: + Type: MQ + Properties: + Broker: + Fn::GetAtt: MyMqBroker.Arn + Queues: + - "TestQueue" + SourceAccessConfigurations: + - Type: BASIC_AUTH + URI: + Ref: MQBrokerUserSecret + + MQBrokerUserSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: MQBrokerUserPassword + SecretString: + Fn::Sub: '{"username":"${MQBrokerUser}","password":"${MQBrokerPassword}"}' + Description: SecretsManager Secret for broker user and password \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_mq_using_autogen_role.yaml b/integration/resources/templates/combination/function_with_mq_using_autogen_role.yaml new file mode 100644 index 0000000000..962bee9d9b --- /dev/null +++ b/integration/resources/templates/combination/function_with_mq_using_autogen_role.yaml @@ -0,0 +1,146 @@ +Parameters: + MQBrokerUser: + Description: The user to access the Amazon MQ broker. + Type: String + Default: testBrokerUser + MinLength: 2 + ConstraintDescription: The Amazon MQ broker user is required ! + MQBrokerPassword: + Description: The password to access the Amazon MQ broker. Min 12 characters + Type: String + Default: testBrokerPassword + MinLength: 12 + ConstraintDescription: The Amazon MQ broker password is required ! + NoEcho: true + +Resources: + MyVpc: + Type: AWS::EC2::VPC + Properties: + CidrBlock: "10.42.0.0/16" + + InternetGateway: + Type: AWS::EC2::InternetGateway + + AttachGateway: + Type: AWS::EC2::VPCGatewayAttachment + Properties: + VpcId: + Ref: MyVpc + InternetGatewayId: + Ref: InternetGateway + RouteTable: + Type: AWS::EC2::RouteTable + Properties: + VpcId: + Ref: MyVpc + + Route: + Type: AWS::EC2::Route + DependsOn: AttachGateway + Properties: + RouteTableId: + Ref: RouteTable + DestinationCidrBlock: '0.0.0.0/0' + GatewayId: + Ref: InternetGateway + PublicSubnet: + Type: AWS::EC2::Subnet + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.42.0.0/24" + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + + PublicSubnetRouteTableAssociation: + Type: AWS::EC2::SubnetRouteTableAssociation + Properties: + SubnetId: + Ref: PublicSubnet + RouteTableId: + Ref: RouteTable + + MQSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Limits security group ingress and egress traffic for the Amazon + MQ instance + VpcId: + Ref: MyVpc + SecurityGroupIngress: + - IpProtocol: tcp + FromPort: 8162 + ToPort: 8162 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 61617 + ToPort: 61617 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 5671 + ToPort: 5671 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 61614 + ToPort: 61614 + CidrIp: '0.0.0.0/0' + - IpProtocol: tcp + FromPort: 8883 + ToPort: 8883 + CidrIp: '0.0.0.0/0' + + MyMqBroker: + Properties: + BrokerName: TestMQBroker2 + DeploymentMode: SINGLE_INSTANCE + EngineType: ACTIVEMQ + EngineVersion: 5.15.12 + HostInstanceType: mq.t3.micro + Logs: + Audit: true + General: true + PubliclyAccessible: true + AutoMinorVersionUpgrade: false + SecurityGroups: + - Ref: MQSecurityGroup + SubnetIds: + - Ref: PublicSubnet + Users: + - ConsoleAccess: true + Groups: + - admin + Username: + Ref: MQBrokerUser + Password: + Ref: MQBrokerPassword + Type: AWS::AmazonMQ::Broker + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs12.x + Handler: index.handler + CodeUri: ${codeuri} + Events: + MyMqEvent: + Type: MQ + Properties: + Broker: + Fn::GetAtt: MyMqBroker.Arn + Queues: + - "TestQueue" + SourceAccessConfigurations: + - Type: BASIC_AUTH + URI: + Ref: MQBrokerUserSecret + + MQBrokerUserSecret: + Type: AWS::SecretsManager::Secret + Properties: + Name: MQBrokerUserPassword2 + SecretString: + Fn::Sub: '{"username":"${MQBrokerUser}","password":"${MQBrokerPassword}"}' + Description: SecretsManager Secret for broker user and password diff --git a/integration/resources/templates/combination/function_with_msk.yaml b/integration/resources/templates/combination/function_with_msk.yaml new file mode 100644 index 0000000000..acaf3a383c --- /dev/null +++ b/integration/resources/templates/combination/function_with_msk.yaml @@ -0,0 +1,109 @@ +Resources: + MyVpc: + Type: "AWS::EC2::VPC" + Properties: + CidrBlock: "10.0.0.0/16" + DependsOn: + - MyLambdaExecutionRole + + MySubnetOne: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.0.0/24" + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + DependsOn: + - MyVpc + + MySubnetTwo: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.1.0/24" + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: "" + DependsOn: + - MyVpc + + MyLambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: ['sts:AssumeRole'] + Effect: Allow + Principal: + Service: [lambda.amazonaws.com] + Policies: + - PolicyName: IntegrationTestExecution + PolicyDocument: + Statement: + - Action: [ 'kafka:DescribeCluster', + 'kafka:GetBootstrapBrokers', + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DescribeVpcs', + 'ec2:DeleteNetworkInterface', + 'ec2:DescribeSubnets', + 'ec2:DescribeSecurityGroups', + 'logs:CreateLogGroup', + 'logs:CreateLogStream', + 'logs:PutLogEvents'] + Effect: Allow + Resource: '*' + ManagedPolicyArns: ['arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'] + Tags: + - {Value: SAM, Key: 'lambda:createdBy'} + + MyMskCluster: + Type: 'AWS::MSK::Cluster' + Properties: + BrokerNodeGroupInfo: + ClientSubnets: + - Ref: MySubnetOne + - Ref: MySubnetTwo + InstanceType: kafka.t3.small + StorageInfo: + EBSStorageInfo: + VolumeSize: 1 + ClusterName: MyMskClusterTestName + KafkaVersion: 2.4.1.1 + NumberOfBrokerNodes: 2 + DependsOn: + - MyVpc + - MySubnetOne + - MySubnetTwo + - MyLambdaExecutionRole + + MyMskStreamProcessor: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs12.x + Handler: index.handler + CodeUri: ${codeuri} + Role: + Fn::GetAtt: [MyLambdaExecutionRole, Arn] + Events: + MyMskEvent: + Type: MSK + Properties: + StartingPosition: LATEST + Stream: + Ref: MyMskCluster + Topics: + - "MyDummyTestTopic" + DependsOn: + - MyVpc + - MySubnetOne + - MySubnetTwo + - MyLambdaExecutionRole + - MyMskCluster + diff --git a/integration/resources/templates/combination/function_with_msk_using_managed_policy.yaml b/integration/resources/templates/combination/function_with_msk_using_managed_policy.yaml new file mode 100644 index 0000000000..0fa07ba4ae --- /dev/null +++ b/integration/resources/templates/combination/function_with_msk_using_managed_policy.yaml @@ -0,0 +1,72 @@ +Resources: + MyVpc: + Type: "AWS::EC2::VPC" + Properties: + CidrBlock: "10.0.0.0/16" + + MySubnetOne: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.0.0/24" + AvailabilityZone: + Fn::Select: + - 0 + - Fn::GetAZs: "" + DependsOn: + - MyVpc + + MySubnetTwo: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.1.0/24" + AvailabilityZone: + Fn::Select: + - 1 + - Fn::GetAZs: "" + DependsOn: + - MyVpc + + MyMskCluster: + Type: 'AWS::MSK::Cluster' + Properties: + BrokerNodeGroupInfo: + ClientSubnets: + - Ref: MySubnetOne + - Ref: MySubnetTwo + InstanceType: kafka.t3.small + StorageInfo: + EBSStorageInfo: + VolumeSize: 1 + ClusterName: MyMskClusterTestName2 + KafkaVersion: 2.4.1.1 + NumberOfBrokerNodes: 2 + DependsOn: + - MyVpc + - MySubnetOne + - MySubnetTwo + + MyMskStreamProcessor: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs12.x + Handler: index.handler + CodeUri: ${codeuri} + Events: + MyMskEvent: + Type: MSK + Properties: + StartingPosition: LATEST + Stream: + Ref: MyMskCluster + Topics: + - "MyDummyTestTopic" + DependsOn: + - MyVpc + - MySubnetOne + - MySubnetTwo + - MyMskCluster + diff --git a/integration/resources/templates/combination/function_with_policy_templates.yaml b/integration/resources/templates/combination/function_with_policy_templates.yaml new file mode 100644 index 0000000000..bd0cec1c0b --- /dev/null +++ b/integration/resources/templates/combination/function_with_policy_templates.yaml @@ -0,0 +1,41 @@ +Parameters: + FunctionNameParam: + Type: String + Default: "somename" + +Conditions: + TrueCondition: + Fn::Equals: ["true", "true"] + +Resources: + + MyFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + Policies: + - SQSPollerPolicy: + QueueName: + Fn::GetAtt: ["MyQueue", "QueueName"] + - LambdaInvokePolicy: + FunctionName: + Ref: FunctionNameParam + + - Fn::If: + - TrueCondition + + - CloudWatchPutMetricPolicy: {} + + - EC2DescribePolicy: {} + + - Fn::If: + - TrueCondition + + - Ref: "AWS::NoValue" + + - EC2DescribePolicy: {} + + MyQueue: + Type: AWS::SQS::Queue diff --git a/integration/resources/templates/combination/function_with_resource_refs.yaml b/integration/resources/templates/combination/function_with_resource_refs.yaml new file mode 100644 index 0000000000..1d091fe916 --- /dev/null +++ b/integration/resources/templates/combination/function_with_resource_refs.yaml @@ -0,0 +1,44 @@ +# Test to verify that resource references available on the Function are properly resolved +# Currently supported references are: +# - Alias +# +# Use them by appending the property to LogicalId of the function + +Resources: + MyLambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + AutoPublishAlias: Live + + MyOtherFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: ${codeuri} + Runtime: python2.7 + Handler: hello.handler + Environment: + Variables: + # Refer to the Alias in another resource + AliasArn: + Ref: MyLambdaFunction.Alias + + +Outputs: + AliasArn: + Value: + Ref: MyLambdaFunction.Alias + + AliasInSub: + Value: + Fn::Sub: ["${MyLambdaFunction.Alias} ${SomeValue}", {"SomeValue": "Alias"}] + + VersionArn: + Value: + Ref: MyLambdaFunction.Version + + VersionNumber: + Value: + Fn::GetAtt: ["MyLambdaFunction.Version", "Version"] diff --git a/integration/resources/templates/combination/function_with_s3.yaml b/integration/resources/templates/combination/function_with_s3.yaml new file mode 100644 index 0000000000..fa60ce17bf --- /dev/null +++ b/integration/resources/templates/combination/function_with_s3.yaml @@ -0,0 +1,18 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: MyBucket + Events: s3:ObjectCreated:* + MyBucket: + Type: AWS::S3::Bucket diff --git a/integration/resources/templates/combination/function_with_schedule.yaml b/integration/resources/templates/combination/function_with_schedule.yaml new file mode 100644 index 0000000000..97cda940ba --- /dev/null +++ b/integration/resources/templates/combination/function_with_schedule.yaml @@ -0,0 +1,37 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Events: + Repeat: + Type: Schedule + Properties: + Schedule: 'rate(5 minutes)' + Input: '{"Hello": "world!"}' + Name: + Fn::Sub: + - TestSchedule${__StackName__} + - __StackName__: + Fn::Select: + - 3 + - Fn::Split: + - "-" + - Ref: AWS::StackName + Description: test schedule + Enabled: True +Outputs: + ScheduleName: + Description: "Name of the cw schedule" + Value: + Fn::Sub: + - TestSchedule${__StackName__} + - __StackName__: + Fn::Select: + - 3 + - Fn::Split: + - "-" + - Ref: AWS::StackName \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_schedule_dlq_and_retry_policy.yaml b/integration/resources/templates/combination/function_with_schedule_dlq_and_retry_policy.yaml new file mode 100644 index 0000000000..28a5ad2b33 --- /dev/null +++ b/integration/resources/templates/combination/function_with_schedule_dlq_and_retry_policy.yaml @@ -0,0 +1,38 @@ +Resources: + MyDeadLetterQueue: + Type: AWS::SQS::Queue + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Events: + Repeat: + Type: Schedule + Properties: + Schedule: 'rate(5 minutes)' + Input: '{"Hello": "world!"}' + Description: test schedule + Enabled: True + DeadLetterConfig: + Arn: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" + RetryPolicy: + MaximumRetryAttempts: 10 + +Outputs: + ScheduleName: + Description: "Name of the cw schedule" + Value: + Ref: MyLambdaFunctionRepeat + MyDLQArn: + Description: "Arn of the dead-letter queue created for the Schedule rule target" + Value: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_schedule_dlq_generated.yaml b/integration/resources/templates/combination/function_with_schedule_dlq_generated.yaml new file mode 100644 index 0000000000..f5eb5581a3 --- /dev/null +++ b/integration/resources/templates/combination/function_with_schedule_dlq_generated.yaml @@ -0,0 +1,40 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Events: + Repeat: + Type: Schedule + Properties: + Schedule: 'rate(5 minutes)' + Input: '{"Hello": "world!"}' + Description: test schedule + Enabled: True + DeadLetterConfig: + Type: SQS + +Outputs: + ScheduleName: + Description: "Name of the cw schedule" + Value: + Ref: MyLambdaFunctionRepeat + MyLambdaArn: + Description: "Arn of the lambda target" + Value: + Fn::GetAtt: + - "MyLambdaFunction" + - "Arn" + MyDLQArn: + Description: "Arn of the dead-letter queue created for the Schedule rule target" + Value: + Fn::GetAtt: + - "MyLambdaFunctionRepeatQueue" + - "Arn" + MyDLQUrl: + Description: "Url of the dead-letter queue created for the Schedule rule target" + Value: + Ref: MyLambdaFunctionRepeatQueue \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_signing_profile.yaml b/integration/resources/templates/combination/function_with_signing_profile.yaml new file mode 100644 index 0000000000..b05c961b14 --- /dev/null +++ b/integration/resources/templates/combination/function_with_signing_profile.yaml @@ -0,0 +1,29 @@ +Resources: + + # a function which has lambda signing configuration + # due to the nature of the flow, we can't sign this package + # and we are setting warning for signing config + MyUnsignedLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + CodeSigningConfigArn: + Ref: MySignedFunctionCodeSigningConfig + + MySignedFunctionCodeSigningConfig: + Type: AWS::Lambda::CodeSigningConfig + Properties: + Description: "Code Signing for MyUnsignedLambdaFunction" + AllowedPublishers: + SigningProfileVersionArns: + - Fn::GetAtt: MySigningProfile.ProfileVersionArn + CodeSigningPolicies: + UntrustedArtifactOnDeployment: "Warn" + + MySigningProfile: + Type: AWS::Signer::SigningProfile + Properties: + PlatformId: AWSLambda-SHA384-ECDSA diff --git a/integration/resources/templates/combination/function_with_sns.yaml b/integration/resources/templates/combination/function_with_sns.yaml new file mode 100644 index 0000000000..7f6f15c3e2 --- /dev/null +++ b/integration/resources/templates/combination/function_with_sns.yaml @@ -0,0 +1,28 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + SNSEvent: + Type: SNS + Properties: + Topic: + Ref: MySnsTopic + + SQSSubscriptionEvent: + Type: SNS + Properties: + Topic: + Ref: MySnsTopic + SqsSubscription: true + + + # This is a CloudFormation SNS resource + MySnsTopic: + Type: AWS::SNS::Topic + diff --git a/integration/resources/templates/combination/function_with_sqs.yaml b/integration/resources/templates/combination/function_with_sqs.yaml new file mode 100644 index 0000000000..6cf183459c --- /dev/null +++ b/integration/resources/templates/combination/function_with_sqs.yaml @@ -0,0 +1,17 @@ +Resources: + MySqsQueueFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Events: + MySqsEvent: + Type: SQS + Properties: + Queue: + Fn::GetAtt: ["MySqsQueue", "Arn"] + BatchSize: 2 + + MySqsQueue: + Type: AWS::SQS::Queue \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_userpool_event.yaml b/integration/resources/templates/combination/function_with_userpool_event.yaml new file mode 100644 index 0000000000..d1d1a7524d --- /dev/null +++ b/integration/resources/templates/combination/function_with_userpool_event.yaml @@ -0,0 +1,55 @@ +Parameters: + CognitoUserPoolName: + Type: String + Default: MyUserPool + +Resources: + MyCognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: + Ref: CognitoUserPoolName + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + Schema: + - AttributeDataType: String + Name: email + Required: false + + PreSignupLambdaFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event, context, callback) => { + event.response = { autoConfirmUser: true } + return event + } + Handler: index.handler + MemorySize: 128 + Runtime: nodejs12.x + Timeout: 3 + Events: + CognitoUserPoolPreSignup: + Type: Cognito + Properties: + UserPool: + Ref: MyCognitoUserPool + Trigger: PreSignUp + +Outputs: + Region: + Description: "Region" + Value: + Ref: AWS::Region + + PreSignupLambdaFunctionArn: + Description: "lambda Function Arn" + Value: + Fn::GetAtt: [PreSignupLambdaFunction, Arn] + CognitoUserPoolId: + Description: "Cognito User Pool Id" + Value: + Ref: MyCognitoUserPool \ No newline at end of file diff --git a/integration/resources/templates/combination/http_api_with_auth.yaml b/integration/resources/templates/combination/http_api_with_auth.yaml new file mode 100644 index 0000000000..265866fc47 --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_auth.yaml @@ -0,0 +1,81 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: python3.7 + InlineCode: | + def handler(event, context): + return {'body': 'Hello World!', 'statusCode': 200} + MemorySize: 128 + Events: + GetApi: + Type: HttpApi + Properties: + Auth: + Authorizer: MyOAuth2Auth + ApiId: + Ref: MyApi + Method: GET + Path: /get + PostApi: + Type: HttpApi + Properties: + Auth: + Authorizer: MyLambdaAuth + ApiId: + Ref: MyApi + Method: POST + Path: /post + DefaultApi: + Type: HttpApi + Properties: + ApiId: + Ref: MyApi + Method: DEFAULT + Path: /default/post + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + print("hello") + Handler: index.handler + Runtime: nodejs12.x + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + Tags: + Tag1: value1 + Tag2: value2 + Auth: + Authorizers: + MyLambdaAuth: + FunctionArn: + Fn::GetAtt: + - MyAuthFn + - Arn + FunctionInvokeRole: + Fn::GetAtt: + - MyAuthFnRole + - Arn + Identity: + Context: + - contextVar + Headers: + - Authorization + QueryStrings: + - petId + StageVariables: + - stageVar + ReauthorizeEvery: 23 + EnableSimpleResponses: true + AuthorizerPayloadFormatVersion: 2.0 + MyOAuth2Auth: + AuthorizationScopes: + - scope4 + JwtConfiguration: + issuer: "https://openid-connect.onelogin.com/oidc" + audience: + - MyApi + IdentitySource: "$request.querystring.param" + DefaultAuthorizer: MyOAuth2Auth \ No newline at end of file diff --git a/integration/resources/templates/combination/http_api_with_auth_updated.yaml b/integration/resources/templates/combination/http_api_with_auth_updated.yaml new file mode 100644 index 0000000000..955245dab5 --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_auth_updated.yaml @@ -0,0 +1,53 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: python3.7 + InlineCode: | + def handler(event, context): + return {'body': 'Hello World!', 'statusCode': 200} + MemorySize: 128 + Events: + PostApi: + Type: HttpApi + Properties: + Auth: + Authorizer: MyLambdaAuthUpdated + ApiId: + Ref: MyApi + Method: POST + Path: /post + + MyAuthFn: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + print("hello") + Handler: index.handler + Runtime: nodejs12.x + + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + Tags: + Tag1: value1 + Tag2: value2 + Auth: + Authorizers: + MyLambdaAuthUpdated: + FunctionArn: + Fn::GetAtt: + - MyAuthFn + - Arn + FunctionInvokeRole: + Fn::GetAtt: + - MyAuthFnRole + - Arn + Identity: + Headers: + - Authorization + ReauthorizeEvery: 37 + EnableSimpleResponses: false + AuthorizerPayloadFormatVersion: 1.0 + DefaultAuthorizer: MyLambdaAuthUpdated \ No newline at end of file diff --git a/integration/resources/templates/combination/http_api_with_cors.yaml b/integration/resources/templates/combination/http_api_with_cors.yaml new file mode 100644 index 0000000000..eecc57f104 --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_cors.yaml @@ -0,0 +1,45 @@ + +Globals: + HttpApi: + CorsConfiguration: + AllowHeaders: + - x-apigateway-header + AllowMethods: + - GET + AllowOrigins: + - https://foo.com + ExposeHeaders: + - x-amzn-header + +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } + } + Handler: index.handler + Runtime: nodejs12.x + Events: + ImplicitApi: + Type: HttpApi + Properties: + Method: GET + Path: /path + TimeoutInMillis: 15000 + PayloadFormatVersion: "1.0" + +Outputs: + ApiUrl: + Description: URL of your API endpoint + Value: + Fn::Sub: 'https://${ServerlessHttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/Prod/' + ApiId: + Description: Api id of ServerlessHttpApi + Value: + Ref: ServerlessHttpApi diff --git a/integration/resources/templates/combination/http_api_with_custom_domains_regional.yaml b/integration/resources/templates/combination/http_api_with_custom_domains_regional.yaml new file mode 100644 index 0000000000..443251fc6e --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_custom_domains_regional.yaml @@ -0,0 +1,59 @@ +Parameters: + MyDomainName: + Type: String + Default: httpapi.sam-gamma-regional.com + MyDomainCert: + Type: String + Default: arn:aws:acm:us-east-1:830899278857:certificate/ae8c894b-4e41-42a6-8817-85d05665d043 + +Globals: + HttpApi: + Domain: + DomainName: + Ref: MyDomainName + CertificateArn: + Ref: MyDomainCert + EndpointConfiguration: REGIONAL + MutualTlsAuthentication: + TruststoreUri: ${mtlsuri} + TruststoreVersion: 0 + SecurityPolicy: TLS_1_2 + BasePath: + - /get + - /post + Route53: + HostedZoneId: Z1DTV8GVAVOHDR + +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: + ImplicitGet: + Type: HttpApi + Properties: + Method: Get + Path: /get + ApiId: + Ref: MyApi + ImplicitPost: + Type: HttpApi + Properties: + Method: Post + Path: /post + ApiId: + Ref: MyApi + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod diff --git a/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_false.yaml b/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_false.yaml new file mode 100644 index 0000000000..2803533d88 --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_false.yaml @@ -0,0 +1,38 @@ +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: + ImplicitGet: + Type: HttpApi + Properties: + Method: Get + Path: /get + ApiId: + Ref: MyApi + ImplicitPost: + Type: HttpApi + Properties: + Method: Post + Path: /post + ApiId: + Ref: MyApi + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + DisableExecuteApiEndpoint: False + StageName: Prod +Outputs: + ApiId: + Value: + Ref: MyApi \ No newline at end of file diff --git a/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_true.yaml b/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_true.yaml new file mode 100644 index 0000000000..85d8bad2cb --- /dev/null +++ b/integration/resources/templates/combination/http_api_with_disable_execute_api_endpoint_true.yaml @@ -0,0 +1,39 @@ +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: + ImplicitGet: + Type: HttpApi + Properties: + Method: Get + Path: /get + ApiId: + Ref: MyApi + ImplicitPost: + Type: HttpApi + Properties: + Method: Post + Path: /post + ApiId: + Ref: MyApi + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + DisableExecuteApiEndpoint: true + StageName: Prod + +Outputs: + ApiId: + Value: + Ref: MyApi \ No newline at end of file diff --git a/integration/resources/templates/combination/implicit_api_with_settings.yaml b/integration/resources/templates/combination/implicit_api_with_settings.yaml new file mode 100644 index 0000000000..118e107d17 --- /dev/null +++ b/integration/resources/templates/combination/implicit_api_with_settings.yaml @@ -0,0 +1,29 @@ +Globals: + Api: + EndpointConfiguration: REGIONAL + BinaryMediaTypes: + - image~1jpg + - image~1png + MethodSettings: [{ + "LoggingLevel": "INFO", + "MetricsEnabled": True, + "DataTraceEnabled": True, + "ResourcePath": "/*", + "HttpMethod": "*" + }] + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + GetApi: + Type: Api + Properties: + Path: /pathget + Method: get diff --git a/integration/resources/templates/combination/intrinsics_code_definition_uri.yaml b/integration/resources/templates/combination/intrinsics_code_definition_uri.yaml new file mode 100644 index 0000000000..18dfc0e8b5 --- /dev/null +++ b/integration/resources/templates/combination/intrinsics_code_definition_uri.yaml @@ -0,0 +1,35 @@ +# Must support explicit bucket, key and version in CodeUri and DefinitionUri parameters + +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + +Resources: + + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs12.x + Handler: index.handler + MemorySize: 128 + CodeUri: + Bucket: + Ref: Bucket + Key: + Ref: CodeKey + + + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: FancyName + DefinitionUri: + Bucket: + Ref: Bucket + Key: + Ref: SwaggerKey + diff --git a/integration/resources/templates/combination/intrinsics_serverless_api.yaml b/integration/resources/templates/combination/intrinsics_serverless_api.yaml new file mode 100644 index 0000000000..0193395f57 --- /dev/null +++ b/integration/resources/templates/combination/intrinsics_serverless_api.yaml @@ -0,0 +1,118 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + MyStageName: + Type: String + Default: devstage + CacheClusterEnabled: + Type: String + Default: "true" + +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + GetApi: + Type: Api + Properties: + Path: /pathget + Method: get + RestApiId: + Ref: MyApi + + PostApi: + Type: Api + Properties: + Path: /pathpost + Method: post + RestApiId: + Ref: MyApi + + Tags: + TagKey1: + Ref: MyStageName + + MyLambdaFunctionFalseCondition: + Type: AWS::Serverless::Function + Condition: FalseCondition + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + GetApi: + Type: Api + Properties: + Path: /pathget + Method: get + RestApiId: + Ref: MyApi + + PostApi: + Type: Api + Properties: + Path: /pathpost + Method: post + RestApiId: + Ref: MyApi + + Tags: + TagKey1: + Ref: MyStageName + + MyApi: + Type: AWS::Serverless::Api + Condition: TrueCondition + Properties: + StageName: + Ref: MyStageName + DefinitionUri: + Bucket: + Ref: Bucket + Key: + Ref: SwaggerKey + Variables: + Var1: + "Fn::Join": ["", ["a", "b"]] + Var2: + "Fn::Join": ["", ["1", "2"]] + + MyApiFalseCondition: + Type: AWS::Serverless::Api + Condition: FalseCondition + Properties: + StageName: + Ref: MyStageName + DefinitionUri: + Bucket: + Ref: Bucket + Key: + Ref: SwaggerKey + Variables: + Var1: + "Fn::Join": ["", ["a", "b"]] + Var2: + "Fn::Join": ["", ["1", "2"]] + diff --git a/integration/resources/templates/combination/intrinsics_serverless_function.yaml b/integration/resources/templates/combination/intrinsics_serverless_function.yaml new file mode 100644 index 0000000000..2ddb10d4f6 --- /dev/null +++ b/integration/resources/templates/combination/intrinsics_serverless_function.yaml @@ -0,0 +1,127 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + MemorySize: + Type: Number + Default: 1024 + Timeout: + Type: Number + Default: 30 + AutoPublishSha: + Type: String + Default: AnyRandomStringWillActuallyDo + +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyFunction: + Type: 'AWS::Serverless::Function' + Condition: TrueCondition + Properties: + CodeUri: + Bucket: + Ref: Bucket + Key: + "Fn::Sub": ["${CodeKey}${extn}", {extn: ""}] + + Handler: + "Fn::Sub": ["${filename}.handler", {filename: "index"}] + + Runtime: + "Fn::Join": ["", ["nodejs", "12.x"]] + + Role: + "Fn::GetAtt": ["MyNewRole", "Arn"] + + Description: "Some description" + + MemorySize: + Ref: MemorySize + + Timeout: + Ref: Timeout + + AutoPublishCodeSha256: + Ref: AutoPublishSha + + Environment: + Variables: + MyRoleArn: + "Fn::GetAtt": ["MyNewRole", "Arn"] + + InputParameter: + Ref: CodeKey + + VpcConfig: + SecurityGroupIds: + - "Fn::GetAtt": ["MySecurityGroup", "GroupId"] + SubnetIds: + - Ref: "MySubnet" + + # Additional resources to reference inside the Function resource + MyNewRole: + Type: AWS::IAM::Role + Properties: + ManagedPolicyArns: + - {"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"} + - {"Fn::Sub": "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"} + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + + + MyVpc: + Type: "AWS::EC2::VPC" + Properties: + CidrBlock: "10.0.0.0/16" + + MySecurityGroup: + Type: "AWS::EC2::SecurityGroup" + Properties: + GroupDescription: "my test group" + VpcId: + Ref: MyVpc + + MySubnet: + Type: "AWS::EC2::Subnet" + Properties: + VpcId: + Ref: MyVpc + CidrBlock: "10.0.0.0/24" + + # False condition, shouldn't be created + MyFunctionFalseCondition: + Type: 'AWS::Serverless::Function' + Condition: FalseCondition + Properties: + CodeUri: + Bucket: + Ref: Bucket + Key: + "Fn::Sub": ["${CodeKey}${extn}", {extn: ""}] + + Handler: + "Fn::Sub": ["${filename}.handler", {filename: "index"}] + + Runtime: + "Fn::Join": ["", ["nodejs", "12.x"]] + + diff --git a/integration/resources/templates/combination/state_machine_with_api.yaml b/integration/resources/templates/combination/state_machine_with_api.yaml new file mode 100644 index 0000000000..23946e3d24 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_api.yaml @@ -0,0 +1,74 @@ +Resources: + + # Create one API resource. This will be referred to by the State machine + ExistingRestApi: + Type: AWS::Serverless::Api + Properties: + StageName: "Dev" + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + + Events: + GetApi: + Type: Api + Properties: + Path: /pathget + Method: get + RestApiId: + Ref: ExistingRestApi + + PostApi: + Type: Api + Properties: + Path: /pathpost + Method: post + +Outputs: + Region: + Description: "Region" + Value: + Ref: AWS::Region + Partition: + Description: "Partition" + Value: + Ref: AWS::Partition + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyImplicitApiRoleName: + Description: "Name of the role created for the implicit Api method" + Value: + Ref: MyStateMachinePostApiRole + MyImplicitApiRoleArn: + Description: "ARN of the role created for the implicit Api method" + Value: + Fn::GetAtt: MyStateMachinePostApiRole.Arn + MyExplicitApiRoleName: + Description: "Name of the role created for the explicit Api method" + Value: + Ref: MyStateMachineGetApiRole + MyExplicitApiRoleArn: + Description: "ARN of the role created for the explicit Api method" + Value: + Fn::GetAtt: MyStateMachineGetApiRole.Arn diff --git a/integration/resources/templates/combination/state_machine_with_cwe.yaml b/integration/resources/templates/combination/state_machine_with_cwe.yaml new file mode 100644 index 0000000000..c2b8a5dcb6 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_cwe.yaml @@ -0,0 +1,45 @@ +Resources: + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyEventName: + Description: "Name of the CloudWatchEvent rule created" + Value: + Ref: MyStateMachineCWEvent + MyEventRole: + Description: "Name of the role created for the CWE rule" + Value: + Ref: MyStateMachineCWEventRole \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_cwe_dlq_generated.yaml b/integration/resources/templates/combination/state_machine_with_cwe_dlq_generated.yaml new file mode 100644 index 0000000000..5a21852269 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_cwe_dlq_generated.yaml @@ -0,0 +1,59 @@ +Resources: + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: SQS + RetryPolicy: + MaximumEventAgeInSeconds: 200 + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyEventName: + Description: "Name of the CloudWatchEvent rule created" + Value: + Ref: MyStateMachineCWEvent + MyEventRole: + Description: "Name of the role created for the CWE rule" + Value: + Ref: MyStateMachineCWEventRole + MyDLQArn: + Description: "Arn of the dead-letter queue created for the CWE rule target" + Value: + Fn::GetAtt: + - "MyStateMachineCWEventQueue" + - "Arn" + MyDLQUrl: + Description: "Url of the dead-letter queue created for the CWE rule target" + Value: + Ref: MyStateMachineCWEventQueue \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_cwe_with_dlq_and_retry_policy.yaml b/integration/resources/templates/combination/state_machine_with_cwe_with_dlq_and_retry_policy.yaml new file mode 100644 index 0000000000..4637164373 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_cwe_with_dlq_and_retry_policy.yaml @@ -0,0 +1,61 @@ +Resources: + MyDeadLetterQueue: + Type: AWS::SQS::Queue + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" + RetryPolicy: + MaximumEventAgeInSeconds: 400 + MaximumRetryAttempts: 5 + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyEventName: + Description: "Name of the CloudWatchEvent rule created" + Value: + Ref: MyStateMachineCWEvent + MyEventRole: + Description: "Name of the role created for the CWE rule" + Value: + Ref: MyStateMachineCWEventRole + MyDLQArn: + Description: "Arn of the dead-letter queue provided for the CWE rule target" + Value: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_policy_templates.yaml b/integration/resources/templates/combination/state_machine_with_policy_templates.yaml new file mode 100644 index 0000000000..5d1ed150d2 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_policy_templates.yaml @@ -0,0 +1,36 @@ +Resources: + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + StartAt: MyTaskState + States: + MyTaskState: + Type: Task + Resource: + Fn::GetAtt: MyFunction.Arn + End: True + Policies: + - SQSPollerPolicy: + QueueName: + Fn::GetAtt: ["MyQueue", "QueueName"] + - LambdaInvokePolicy: + FunctionName: + Ref: MyFunction + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ${codeuri} + Handler: hello.handler + Runtime: python2.7 + + MyQueue: + Type: AWS::SQS::Queue + +Outputs: + MyStateMachineRole: + Description: "ARN of the role created for the State Machine" + Value: + Ref: MyStateMachineRole \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_schedule.yaml b/integration/resources/templates/combination/state_machine_with_schedule.yaml new file mode 100644 index 0000000000..7f88689024 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_schedule.yaml @@ -0,0 +1,45 @@ +Resources: + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Description: test schedule + Enabled: False + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyScheduleName: + Description: "Name of the Schedule rule created" + Value: + Ref: MyStateMachineCWSchedule + MyEventRole: + Description: "ARN of the role created for the Schedule rule" + Value: + Ref: MyStateMachineCWScheduleRole \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_schedule_dlq_and_retry_policy.yaml b/integration/resources/templates/combination/state_machine_with_schedule_dlq_and_retry_policy.yaml new file mode 100644 index 0000000000..5abae8a4a8 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_schedule_dlq_and_retry_policy.yaml @@ -0,0 +1,61 @@ +Resources: + MyDeadLetterQueue: + Type: AWS::SQS::Queue + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Description: test schedule + Enabled: False + DeadLetterConfig: + Arn: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" + RetryPolicy: + MaximumRetryAttempts: 2 + + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyScheduleName: + Description: "Name of the Schedule rule created" + Value: + Ref: MyStateMachineCWSchedule + MyEventRole: + Description: "ARN of the role created for the Schedule rule" + Value: + Ref: MyStateMachineCWScheduleRole + MyDLQArn: + Description: "Arn of the dead-letter queue created for the Schedule rule target" + Value: + Fn::GetAtt: + - "MyDeadLetterQueue" + - "Arn" \ No newline at end of file diff --git a/integration/resources/templates/combination/state_machine_with_schedule_dlq_generated.yaml b/integration/resources/templates/combination/state_machine_with_schedule_dlq_generated.yaml new file mode 100644 index 0000000000..ec84d43877 --- /dev/null +++ b/integration/resources/templates/combination/state_machine_with_schedule_dlq_generated.yaml @@ -0,0 +1,58 @@ +Resources: + + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + + Events: + CWSchedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Description: test schedule + Enabled: False + DeadLetterConfig: + Type: SQS + QueueLogicalId: MyDlq + +Outputs: + MyStateMachineArn: + Description: "ARN of the State Machine" + Value: + Ref: MyStateMachine + MyScheduleName: + Description: "Name of the Schedule rule created" + Value: + Ref: MyStateMachineCWSchedule + MyEventRole: + Description: "ARN of the role created for the Schedule rule" + Value: + Ref: MyStateMachineCWScheduleRole + MyDLQArn: + Description: "Arn of the dead-letter queue created for the Schedule rule target" + Value: + Fn::GetAtt: + - "MyDlq" + - "Arn" + MyDLQUrl: + Description: "Url of the dead-letter queue created for the Schedule rule target" + Value: + Ref: MyDlq \ No newline at end of file diff --git a/integration/single/test_basic_api.py b/integration/single/test_basic_api.py index a54fb1fd39..846d967189 100644 --- a/integration/single/test_basic_api.py +++ b/integration/single/test_basic_api.py @@ -11,7 +11,7 @@ def test_basic_api(self): """ Creates an API and updates its DefinitionUri """ - self.create_and_verify_stack("basic_api") + self.create_and_verify_stack("single/basic_api") first_dep_ids = self.get_stack_deployment_ids() self.assertEqual(len(first_dep_ids), 1) @@ -30,7 +30,7 @@ def test_basic_api_with_mode(self): Creates an API and updates its DefinitionUri """ # Create an API with get and put - self.create_and_verify_stack("basic_api_with_mode") + self.create_and_verify_stack("single/basic_api_with_mode") stack_output = self.get_stack_outputs() api_endpoint = stack_output.get("ApiEndpoint") @@ -38,7 +38,7 @@ def test_basic_api_with_mode(self): self.assertEqual(response.status_code, 200) # Removes get from the API - self.update_and_verify_stack("basic_api_with_mode_update") + self.update_and_verify_stack("single/basic_api_with_mode_update") response = requests.get(f"{api_endpoint}/get") # API Gateway by default returns 403 if a path do not exist self.assertEqual(response.status_code, 403) @@ -47,7 +47,7 @@ def test_basic_api_inline_openapi(self): """ Creates an API with and inline OpenAPI and updates its DefinitionBody basePath """ - self.create_and_verify_stack("basic_api_inline_openapi") + self.create_and_verify_stack("single/basic_api_inline_openapi") first_dep_ids = self.get_stack_deployment_ids() self.assertEqual(len(first_dep_ids), 1) @@ -67,7 +67,7 @@ def test_basic_api_inline_swagger(self): """ Creates an API with an inline Swagger and updates its DefinitionBody basePath """ - self.create_and_verify_stack("basic_api_inline_swagger") + self.create_and_verify_stack("single/basic_api_inline_swagger") first_dep_ids = self.get_stack_deployment_ids() self.assertEqual(len(first_dep_ids), 1) @@ -87,7 +87,7 @@ def test_basic_api_with_tags(self): """ Creates an API with tags """ - self.create_and_verify_stack("basic_api_with_tags") + self.create_and_verify_stack("single/basic_api_with_tags") stages = self.get_api_stack_stages() self.assertEqual(len(stages), 2) diff --git a/integration/single/test_basic_application.py b/integration/single/test_basic_application.py index 43dc9fdfa7..15ae0861f3 100644 --- a/integration/single/test_basic_application.py +++ b/integration/single/test_basic_application.py @@ -17,7 +17,7 @@ def test_basic_application_s3_location(self): Creates an application with its properties defined as a template file in a S3 bucket """ - self.create_and_verify_stack("basic_application_s3_location") + self.create_and_verify_stack("single/basic_application_s3_location") nested_stack_resource = self.get_stack_nested_stack_resources() tables = self.get_stack_resources("AWS::DynamoDB::Table", nested_stack_resource) @@ -32,7 +32,7 @@ def test_basic_application_sar_location(self): """ Creates an application with a lamda function """ - self.create_and_verify_stack("basic_application_sar_location") + self.create_and_verify_stack("single/basic_application_sar_location") nested_stack_resource = self.get_stack_nested_stack_resources() functions = self.get_stack_resources("AWS::Lambda::Function", nested_stack_resource) @@ -48,7 +48,7 @@ def test_basic_application_sar_location_with_intrinsics(self): Creates an application with a lambda function with intrinsics """ expected_function_name = "helloworldpython" if self.get_region() == "us-east-1" else "helloworldpython3" - self.create_and_verify_stack("basic_application_sar_location_with_intrinsics") + self.create_and_verify_stack("single/basic_application_sar_location_with_intrinsics") nested_stack_resource = self.get_stack_nested_stack_resources() functions = self.get_stack_resources("AWS::Lambda::Function", nested_stack_resource) diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index 888ec66672..11ef5809a5 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -14,9 +14,9 @@ class TestBasicFunction(BaseTest): @parameterized.expand( [ - "basic_function", - "basic_function_no_envvar", - "basic_function_openapi", + "single/basic_function", + "single/basic_function_no_envvar", + "single/basic_function_openapi", ] ) def test_basic_function(self, file_name): @@ -33,8 +33,8 @@ def test_basic_function(self, file_name): @parameterized.expand( [ - "function_with_http_api_events", - "function_alias_with_http_api_events", + "single/function_with_http_api_events", + "single/function_alias_with_http_api_events", ] ) def test_function_with_http_api_events(self, file_name): @@ -45,12 +45,12 @@ def test_function_with_http_api_events(self, file_name): self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT) def test_function_with_deployment_preference_alarms_intrinsic_if(self): - self.create_and_verify_stack("function_with_deployment_preference_alarms_intrinsic_if") + self.create_and_verify_stack("single/function_with_deployment_preference_alarms_intrinsic_if") @parameterized.expand( [ - ("basic_function_with_sns_dlq", "sns:Publish"), - ("basic_function_with_sqs_dlq", "sqs:SendMessage"), + ("single/basic_function_with_sns_dlq", "sns:Publish"), + ("single/basic_function_with_sqs_dlq", "sqs:SendMessage"), ] ) def test_basic_function_with_dlq(self, file_name, action): @@ -83,7 +83,7 @@ def test_basic_function_with_kms_key_arn(self): """ Creates a basic lambda function with KMS key arn """ - self.create_and_verify_stack("basic_function_with_kmskeyarn") + self.create_and_verify_stack("single/basic_function_with_kmskeyarn") lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") function_configuration = self.client_provider.lambda_client.get_function_configuration( @@ -97,7 +97,7 @@ def test_basic_function_with_tags(self): """ Creates a basic lambda function with tags """ - self.create_and_verify_stack("basic_function_with_tags") + self.create_and_verify_stack("single/basic_function_with_tags") lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") get_function_result = self.client_provider.lambda_client.get_function(FunctionName=lambda_function_name) tags = get_function_result["Tags"] @@ -114,7 +114,7 @@ def test_basic_function_event_destinations(self): """ Creates a basic lambda function with event destinations """ - self.create_and_verify_stack("basic_function_event_destinations") + self.create_and_verify_stack("single/basic_function_event_destinations") test_function_1 = self.get_physical_id_by_logical_id("MyTestFunction") test_function_2 = self.get_physical_id_by_logical_id("MyTestFunction2") @@ -158,27 +158,7 @@ def test_basic_function_with_tracing(self): """ Creates a basic lambda function with tracing """ - parameters = [ - { - "ParameterKey": "Bucket", - "ParameterValue": self.s3_bucket_name, - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - { - "ParameterKey": "CodeKey", - "ParameterValue": "code.zip", - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - { - "ParameterKey": "SwaggerKey", - "ParameterValue": "swagger1.json", - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - ] - self.create_and_verify_stack("basic_function_with_tracing", parameters) + self.create_and_verify_stack("single/basic_function_with_tracing", self.get_default_test_template_parameters()) active_tracing_function_id = self.get_physical_id_by_logical_id("ActiveTracingFunction") pass_through_tracing_function_id = self.get_physical_id_by_logical_id("PassThroughTracingFunction") diff --git a/integration/single/test_basic_http_api.py b/integration/single/test_basic_http_api.py index e7f3c187d0..e7cc3d9560 100644 --- a/integration/single/test_basic_http_api.py +++ b/integration/single/test_basic_http_api.py @@ -14,7 +14,7 @@ def test_basic_http_api(self): """ Creates a HTTP API """ - self.create_and_verify_stack("basic_http_api") + self.create_and_verify_stack("single/basic_http_api") stages = self.get_api_v2_stack_stages() diff --git a/integration/single/test_basic_layer_version.py b/integration/single/test_basic_layer_version.py index e5f2421f41..de76eab94f 100644 --- a/integration/single/test_basic_layer_version.py +++ b/integration/single/test_basic_layer_version.py @@ -14,7 +14,7 @@ def test_basic_layer_version(self): """ Creates a basic lambda layer version """ - self.create_and_verify_stack("basic_layer") + self.create_and_verify_stack("single/basic_layer") layer_logical_id_1 = self.get_logical_id_by_type("AWS::Lambda::LayerVersion") @@ -31,7 +31,7 @@ def test_basic_layer_with_parameters(self): """ Creates a basic lambda layer version with parameters """ - self.create_and_verify_stack("basic_layer_with_parameters") + self.create_and_verify_stack("single/basic_layer_with_parameters") outputs = self.get_stack_outputs() layer_arn = outputs["MyLayerArn"] diff --git a/integration/single/test_basic_state_machine.py b/integration/single/test_basic_state_machine.py index d5ff568222..2cadd5acbc 100644 --- a/integration/single/test_basic_state_machine.py +++ b/integration/single/test_basic_state_machine.py @@ -13,14 +13,14 @@ def test_basic_state_machine_inline_definition(self): """ Creates a State Machine from inline definition """ - self.create_and_verify_stack("basic_state_machine_inline_definition") + self.create_and_verify_stack("single/basic_state_machine_inline_definition") @skipIf(current_region_does_not_support(["XRay"]), "XRay is not supported in this testing region") def test_basic_state_machine_with_tags(self): """ Creates a State Machine with tags """ - self.create_and_verify_stack("basic_state_machine_with_tags") + self.create_and_verify_stack("single/basic_state_machine_with_tags") tags = self.get_stack_tags("MyStateMachineArn") diff --git a/requirements/dev.txt b/requirements/dev.txt index 193b157bbb..e9f3e9b82a 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -15,6 +15,8 @@ parameterized~=0.7.4 pathlib2>=2.3.5; python_version < '3' click~=7.1 dateparser~=0.7 +pillow~=6.2.2 +boto3~=1.17 # Requirements for examples requests~=2.24.0 From 7ee8c602d91ca7f9bbb235e34ec10a2b85f11f01 Mon Sep 17 00:00:00 2001 From: mingkun2020 <68391979+mingkun2020@users.noreply.github.com> Date: Fri, 9 Jul 2021 17:14:48 -0700 Subject: [PATCH 16/28] fix: Compare integration tests results using hash of file content instead of image compare (#2086) * change yaml.load to yaml.safe_load for the security best practice * use yaml_parse for consistant style * remove pillow library for image comparing, use hash instead * make it compatible with py2 --- integration/combination/test_api_settings.py | 14 ++++++++------ requirements/dev.txt | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/integration/combination/test_api_settings.py b/integration/combination/test_api_settings.py index 7a9fd95745..5752ba9ae3 100644 --- a/integration/combination/test_api_settings.py +++ b/integration/combination/test_api_settings.py @@ -1,4 +1,4 @@ -from io import BytesIO +import hashlib try: from pathlib import Path @@ -10,8 +10,6 @@ from integration.helpers.base_test import BaseTest -from PIL import Image - class TestApiSettings(BaseTest): def test_method_settings(self): @@ -163,11 +161,15 @@ def verify_binary_media_request(self, url, expected_status_code): response = requests.get(url, headers=headers) status = response.status_code - expected = Image.open(Path(self.code_dir, "AWS_logo_RGB.png")) + expected_file_path = str(Path(self.code_dir, "AWS_logo_RGB.png")) + + with open(expected_file_path, mode="rb") as file: + expected_file_content = file.read() + expected_hash = hashlib.sha1(expected_file_content).hexdigest() if 200 <= status <= 299: - actual = Image.open(BytesIO(response.content)) - self.assertEqual(expected, actual) + actual_hash = hashlib.sha1(response.content).hexdigest() + self.assertEqual(expected_hash, actual_hash) self.assertEqual(status, expected_status_code, " must return HTTP " + str(expected_status_code)) diff --git a/requirements/dev.txt b/requirements/dev.txt index e9f3e9b82a..1424f80d6e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -15,7 +15,6 @@ parameterized~=0.7.4 pathlib2>=2.3.5; python_version < '3' click~=7.1 dateparser~=0.7 -pillow~=6.2.2 boto3~=1.17 # Requirements for examples From d1590f8290b2392bab0a353305ec71d707473945 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Thu, 15 Jul 2021 13:30:14 -0700 Subject: [PATCH 17/28] Remove logging of SAR service call response (#2064) --- samtranslator/plugins/application/serverless_app_plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/samtranslator/plugins/application/serverless_app_plugin.py b/samtranslator/plugins/application/serverless_app_plugin.py index 05741c5dfb..fa3ba36014 100644 --- a/samtranslator/plugins/application/serverless_app_plugin.py +++ b/samtranslator/plugins/application/serverless_app_plugin.py @@ -360,7 +360,6 @@ def _sar_service_call(self, service_call_lambda, logical_id, *args): """ try: response = service_call_lambda(*args) - LOG.info(response) return response except ClientError as e: error_code = e.response["Error"]["Code"] From 5a85aea215d52142838556538329789b95b06786 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Fri, 16 Jul 2021 08:53:27 -0700 Subject: [PATCH 18/28] Test CloudWatchEvent Properties With Intrinsic Functions (#2090) * Add headers whenever cors is set * Add cw event success cases * Replace GetAtt with condition * Cleanup test yaml * Cleanup test yaml * Remove files * Add intrinsic to pattern field * Fix wrong files committed --- .../input/cloudwatchevent_intrinsics.yaml | 31 ++++ .../aws-cn/cloudwatchevent_intrinsics.json | 147 ++++++++++++++++++ .../cloudwatchevent_intrinsics.json | 147 ++++++++++++++++++ .../output/cloudwatchevent_intrinsics.json | 147 ++++++++++++++++++ tests/translator/test_translator.py | 1 + 5 files changed, 473 insertions(+) create mode 100644 tests/translator/input/cloudwatchevent_intrinsics.yaml create mode 100644 tests/translator/output/aws-cn/cloudwatchevent_intrinsics.json create mode 100644 tests/translator/output/aws-us-gov/cloudwatchevent_intrinsics.json create mode 100644 tests/translator/output/cloudwatchevent_intrinsics.json diff --git a/tests/translator/input/cloudwatchevent_intrinsics.yaml b/tests/translator/input/cloudwatchevent_intrinsics.yaml new file mode 100644 index 0000000000..57d0c5893e --- /dev/null +++ b/tests/translator/input/cloudwatchevent_intrinsics.yaml @@ -0,0 +1,31 @@ +Parameters: + PathA: + Type: String + Default: "SomeInputPath" + PathB: + Type: String + Default: "AnotherInputPath" + +Conditions: + PathCondition: + Fn::Equals: + - true + - true + +Resources: + LambdaFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: CloudWatchEvent + Properties: + EventBusName: !Join [ "", [ "Event", "Bus", "Name" ] ] + Input: !Join [ ":", [ "{ Key", "Value }" ] ] + InputPath: !If [ PathCondition, !Ref PathA, !Ref PathB ] + Pattern: + detail: + state: !Split [ "," , "terminated,stopped" ] \ No newline at end of file diff --git a/tests/translator/output/aws-cn/cloudwatchevent_intrinsics.json b/tests/translator/output/aws-cn/cloudwatchevent_intrinsics.json new file mode 100644 index 0000000000..d761fc5e07 --- /dev/null +++ b/tests/translator/output/aws-cn/cloudwatchevent_intrinsics.json @@ -0,0 +1,147 @@ +{ + "Parameters": { + "PathA": { + "Type": "String", + "Default": "SomeInputPath" + }, + "PathB": { + "Type": "String", + "Default": "AnotherInputPath" + } + }, + "Conditions": { + "PathCondition": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "LambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionRole": { + "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": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventBusName": { + "Fn::Join": [ + "", + [ + "Event", + "Bus", + "Name" + ] + ] + }, + "EventPattern": { + "detail": { + "state": { + "Fn::Split": [ + ",", + "terminated,stopped" + ] + } + } + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "LambdaFunction", + "Arn" + ] + }, + "Id": "LambdaFunctionOnTerminateLambdaTarget", + "Input": { + "Fn::Join": [ + ":", + [ + "{ Key", + "Value }" + ] + ] + }, + "InputPath": { + "Fn::If": [ + "PathCondition", + { + "Ref": "PathA" + }, + { + "Ref": "PathB" + } + ] + } + } + ] + } + }, + "LambdaFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "LambdaFunction" + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "LambdaFunctionOnTerminate", + "Arn" + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/cloudwatchevent_intrinsics.json b/tests/translator/output/aws-us-gov/cloudwatchevent_intrinsics.json new file mode 100644 index 0000000000..c8d97aea65 --- /dev/null +++ b/tests/translator/output/aws-us-gov/cloudwatchevent_intrinsics.json @@ -0,0 +1,147 @@ +{ + "Parameters": { + "PathA": { + "Type": "String", + "Default": "SomeInputPath" + }, + "PathB": { + "Type": "String", + "Default": "AnotherInputPath" + } + }, + "Conditions": { + "PathCondition": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "LambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionRole": { + "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": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventBusName": { + "Fn::Join": [ + "", + [ + "Event", + "Bus", + "Name" + ] + ] + }, + "EventPattern": { + "detail": { + "state": { + "Fn::Split": [ + ",", + "terminated,stopped" + ] + } + } + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "LambdaFunction", + "Arn" + ] + }, + "Id": "LambdaFunctionOnTerminateLambdaTarget", + "Input": { + "Fn::Join": [ + ":", + [ + "{ Key", + "Value }" + ] + ] + }, + "InputPath": { + "Fn::If": [ + "PathCondition", + { + "Ref": "PathA" + }, + { + "Ref": "PathB" + } + ] + } + } + ] + } + }, + "LambdaFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "LambdaFunction" + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "LambdaFunctionOnTerminate", + "Arn" + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/cloudwatchevent_intrinsics.json b/tests/translator/output/cloudwatchevent_intrinsics.json new file mode 100644 index 0000000000..1a12bb7851 --- /dev/null +++ b/tests/translator/output/cloudwatchevent_intrinsics.json @@ -0,0 +1,147 @@ +{ + "Parameters": { + "PathA": { + "Type": "String", + "Default": "SomeInputPath" + }, + "PathB": { + "Type": "String", + "Default": "AnotherInputPath" + } + }, + "Conditions": { + "PathCondition": { + "Fn::Equals": [ + true, + true + ] + } + }, + "Resources": { + "LambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionRole": { + "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": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "LambdaFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventBusName": { + "Fn::Join": [ + "", + [ + "Event", + "Bus", + "Name" + ] + ] + }, + "EventPattern": { + "detail": { + "state": { + "Fn::Split": [ + ",", + "terminated,stopped" + ] + } + } + }, + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "LambdaFunction", + "Arn" + ] + }, + "Id": "LambdaFunctionOnTerminateLambdaTarget", + "Input": { + "Fn::Join": [ + ":", + [ + "{ Key", + "Value }" + ] + ] + }, + "InputPath": { + "Fn::If": [ + "PathCondition", + { + "Ref": "PathA" + }, + { + "Ref": "PathB" + } + ] + } + } + ] + } + }, + "LambdaFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "LambdaFunction" + }, + "Principal": "events.amazonaws.com", + "SourceArn": { + "Fn::GetAtt": [ + "LambdaFunctionOnTerminate", + "Arn" + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index e22bff999d..0db19f45a8 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -267,6 +267,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "application_with_intrinsics", "basic_layer", "cloudwatchevent", + "cloudwatchevent_intrinsics", "eventbridgerule", "eventbridgerule_with_dlq", "eventbridgerule_with_retry_policy", From 018c3a5bc7cbba119948a0035bc8c6cc88fab218 Mon Sep 17 00:00:00 2001 From: Mathieu Grandis <73313235+mgrandis@users.noreply.github.com> Date: Thu, 22 Jul 2021 17:04:27 -0700 Subject: [PATCH 19/28] test: Added intrinsic tests for Function SNS Event (#2101) --- .../combination/test_function_with_sns.py | 22 ++- integration/helpers/resource.py | 16 +- .../function_with_sns_intrinsics.json | 30 +++ .../function_with_sns_intrinsics.yaml | 38 ++++ .../input/error_sns_intrinsics.yaml | 23 +++ tests/translator/input/sns_intrinsics.yaml | 41 +++++ .../output/aws-cn/sns_intrinsics.json | 171 ++++++++++++++++++ .../output/aws-us-gov/sns_intrinsics.json | 171 ++++++++++++++++++ .../output/error_sns_intrinsics.json | 8 + tests/translator/output/sns_intrinsics.json | 171 ++++++++++++++++++ tests/translator/test_translator.py | 2 + 11 files changed, 688 insertions(+), 5 deletions(-) create mode 100644 integration/resources/expected/combination/function_with_sns_intrinsics.json create mode 100644 integration/resources/templates/combination/function_with_sns_intrinsics.yaml create mode 100644 tests/translator/input/error_sns_intrinsics.yaml create mode 100644 tests/translator/input/sns_intrinsics.yaml create mode 100644 tests/translator/output/aws-cn/sns_intrinsics.json create mode 100644 tests/translator/output/aws-us-gov/sns_intrinsics.json create mode 100644 tests/translator/output/error_sns_intrinsics.json create mode 100644 tests/translator/output/sns_intrinsics.json diff --git a/integration/combination/test_function_with_sns.py b/integration/combination/test_function_with_sns.py index fa90c1ee84..9f7dd597e5 100644 --- a/integration/combination/test_function_with_sns.py +++ b/integration/combination/test_function_with_sns.py @@ -17,7 +17,7 @@ def test_function_with_sns_bucket_trigger(self): lambda_subscription = next((x for x in subscriptions if x["Protocol"] == "lambda"), None) self.assertIsNotNone(lambda_subscription) - self.assertTrue(lambda_function_endpoint in lambda_subscription["Endpoint"]) + self.assertIn(lambda_function_endpoint, lambda_subscription["Endpoint"]) self.assertEqual(lambda_subscription["Protocol"], "lambda") self.assertEqual(lambda_subscription["TopicArn"], sns_topic_arn) @@ -26,3 +26,23 @@ def test_function_with_sns_bucket_trigger(self): self.assertIsNotNone(sqs_subscription) self.assertEqual(sqs_subscription["Protocol"], "sqs") self.assertEqual(sqs_subscription["TopicArn"], sns_topic_arn) + + def test_function_with_sns_intrinsics(self): + self.create_and_verify_stack("combination/function_with_sns_intrinsics") + + sns_client = self.client_provider.sns_client + + sns_topic_arn = self.get_physical_id_by_type("AWS::SNS::Topic") + + subscriptions = sns_client.list_subscriptions_by_topic(TopicArn=sns_topic_arn)["Subscriptions"] + self.assertEqual(len(subscriptions), 1) + + subscription = subscriptions[0] + + self.assertIsNotNone(subscription) + self.assertEqual(subscription["Protocol"], "sqs") + self.assertEqual(subscription["TopicArn"], sns_topic_arn) + + subscription_arn = subscription["SubscriptionArn"] + subscription_attributes = sns_client.get_subscription_attributes(SubscriptionArn=subscription_arn) + self.assertEqual(subscription_attributes["Attributes"]["FilterPolicy"], '{"price_usd":[{"numeric":["<",100]}]}') diff --git a/integration/helpers/resource.py b/integration/helpers/resource.py index d1150fc159..6a31d1a771 100644 --- a/integration/helpers/resource.py +++ b/integration/helpers/resource.py @@ -41,7 +41,9 @@ def verify_stack_resources(expected_file_path, stack_resources): parsed_resources = _sort_resources(stack_resources["StackResourceSummaries"]) if len(expected_resources) != len(parsed_resources): - return "'{}' resources expected, '{}' found".format(len(expected_resources), len(parsed_resources)) + return "'{}' resources expected, '{}' found: \n{}".format( + len(expected_resources), len(parsed_resources), json.dumps(parsed_resources, default=str) + ) for i in range(len(expected_resources)): exp = expected_resources[i] @@ -55,7 +57,7 @@ def verify_stack_resources(expected_file_path, stack_resources): "ResourceType": parsed["ResourceType"], } - return "'{}' expected, '{}' found (Resources must appear in the same order, don't include the LogicalId random suffix)".format( + return "'{}' expected, '{}' found (Don't include the LogicalId random suffix)".format( exp, parsed_trimed_down ) if exp["ResourceType"] != parsed["ResourceType"]: @@ -79,7 +81,8 @@ def generate_suffix(): def _sort_resources(resources): """ - Sorts a stack's resources by LogicalResourceId + Filters and sorts a stack's resources by LogicalResourceId. + Keeps only the LogicalResourceId and ResourceType properties Parameters ---------- @@ -93,7 +96,12 @@ def _sort_resources(resources): """ if resources is None: return [] - return sorted(resources, key=lambda d: d["LogicalResourceId"]) + + filtered_resources = map( + lambda x: {"LogicalResourceId": x["LogicalResourceId"], "ResourceType": x["ResourceType"]}, resources + ) + + return sorted(filtered_resources, key=lambda d: d["LogicalResourceId"]) def create_bucket(bucket_name, region): diff --git a/integration/resources/expected/combination/function_with_sns_intrinsics.json b/integration/resources/expected/combination/function_with_sns_intrinsics.json new file mode 100644 index 0000000000..8d715ef5d1 --- /dev/null +++ b/integration/resources/expected/combination/function_with_sns_intrinsics.json @@ -0,0 +1,30 @@ +[ + { + "LogicalResourceId": "MyLambdaFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyLambdaFunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MySnsTopic", + "ResourceType": "AWS::SNS::Topic" + }, + { + "LogicalResourceId": "MyLambdaFunctionSNSEvent", + "ResourceType": "AWS::SNS::Subscription" + }, + { + "LogicalResourceId": "MyLambdaFunctionSNSEventQueue", + "ResourceType": "AWS::SQS::Queue" + }, + { + "LogicalResourceId": "MyLambdaFunctionSNSEventEventSourceMapping", + "ResourceType": "AWS::Lambda::EventSourceMapping" + }, + { + "LogicalResourceId": "MyLambdaFunctionSNSEventQueuePolicy", + "ResourceType": "AWS::SQS::QueuePolicy" + } +] \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_sns_intrinsics.yaml b/integration/resources/templates/combination/function_with_sns_intrinsics.yaml new file mode 100644 index 0000000000..08c9f5872e --- /dev/null +++ b/integration/resources/templates/combination/function_with_sns_intrinsics.yaml @@ -0,0 +1,38 @@ +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + SNSEvent: + Type: SNS + Properties: + Topic: + Ref: MySnsTopic + FilterPolicy: + Fn::If: + - MyCondition + - price_usd: + - numeric: + - ">=" + - 100 + - price_usd: + - numeric: + - "<" + - 100 + Region: + Ref: AWS::Region + SqsSubscription: true + + MySnsTopic: + Type: AWS::SNS::Topic diff --git a/tests/translator/input/error_sns_intrinsics.yaml b/tests/translator/input/error_sns_intrinsics.yaml new file mode 100644 index 0000000000..668a0aae81 --- /dev/null +++ b/tests/translator/input/error_sns_intrinsics.yaml @@ -0,0 +1,23 @@ +Parameters: + SnsSqsSubscription: + Type: Boolean + Default: false + +Resources: + SaveNotificationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/notifications.zip + Handler: index.save_notification + Runtime: nodejs12.x + Events: + NotificationTopic: + Type: SNS + Properties: + SqsSubscription: + Ref: SnsSqsSubscription + Topic: + Ref: Notifications + + Notifications: + Type: AWS::SNS::Topic diff --git a/tests/translator/input/sns_intrinsics.yaml b/tests/translator/input/sns_intrinsics.yaml new file mode 100644 index 0000000000..cdafe672c6 --- /dev/null +++ b/tests/translator/input/sns_intrinsics.yaml @@ -0,0 +1,41 @@ +Parameters: + SnsRegion: + Type: String + Default: us-east-1 + +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + SaveNotificationFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/notifications.zip + Handler: index.save_notification + Runtime: nodejs12.x + Events: + NotificationTopic: + Type: SNS + Properties: + FilterPolicy: + Fn::If: + - MyCondition + - price_usd: + - numeric: + - ">=" + - 100 + - price_usd: + - numeric: + - "<" + - 100 + Region: + Ref: SnsRegion + SqsSubscription: true + Topic: + Ref: Notifications + + Notifications: + Type: AWS::SNS::Topic diff --git a/tests/translator/output/aws-cn/sns_intrinsics.json b/tests/translator/output/aws-cn/sns_intrinsics.json new file mode 100644 index 0000000000..b65f7eddfa --- /dev/null +++ b/tests/translator/output/aws-cn/sns_intrinsics.json @@ -0,0 +1,171 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "SnsRegion": { + "Default": "us-east-1", + "Type": "String" + } + }, + "Resources": { + "Notifications": { + "Type": "AWS::SNS::Topic" + }, + "SaveNotificationFunctionNotificationTopicQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "SaveNotificationFunctionNotificationTopicQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "SaveNotificationFunctionNotificationTopicQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + } + }, + "SaveNotificationFunctionNotificationTopic": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "FilterPolicy": { + "Fn::If": [ + "MyCondition", + { + "price_usd": [ + { + "numeric": [ + ">=", + 100 + ] + } + ] + }, + { + "price_usd": [ + { + "numeric": [ + "<", + 100 + ] + } + ] + } + ] + }, + "Region": { + "Ref": "SnsRegion" + }, + "Endpoint": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + } + }, + "SaveNotificationFunctionRole": { + "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", + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "SaveNotificationFunctionNotificationTopicEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "SaveNotificationFunction" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + } + } + }, + "SaveNotificationFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.save_notification", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "notifications.zip" + }, + "Role": { + "Fn::GetAtt": [ + "SaveNotificationFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/sns_intrinsics.json b/tests/translator/output/aws-us-gov/sns_intrinsics.json new file mode 100644 index 0000000000..0df391edf5 --- /dev/null +++ b/tests/translator/output/aws-us-gov/sns_intrinsics.json @@ -0,0 +1,171 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "SnsRegion": { + "Default": "us-east-1", + "Type": "String" + } + }, + "Resources": { + "Notifications": { + "Type": "AWS::SNS::Topic" + }, + "SaveNotificationFunctionNotificationTopicQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "SaveNotificationFunctionNotificationTopicQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "SaveNotificationFunctionNotificationTopicQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + } + }, + "SaveNotificationFunctionNotificationTopic": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "FilterPolicy": { + "Fn::If": [ + "MyCondition", + { + "price_usd": [ + { + "numeric": [ + ">=", + 100 + ] + } + ] + }, + { + "price_usd": [ + { + "numeric": [ + "<", + 100 + ] + } + ] + } + ] + }, + "Region": { + "Ref": "SnsRegion" + }, + "Endpoint": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + } + }, + "SaveNotificationFunctionRole": { + "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", + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "SaveNotificationFunctionNotificationTopicEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "SaveNotificationFunction" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + } + } + }, + "SaveNotificationFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.save_notification", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "notifications.zip" + }, + "Role": { + "Fn::GetAtt": [ + "SaveNotificationFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_sns_intrinsics.json b/tests/translator/output/error_sns_intrinsics.json new file mode 100644 index 0000000000..ad95a1a668 --- /dev/null +++ b/tests/translator/output/error_sns_intrinsics.json @@ -0,0 +1,8 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [SaveNotificationFunction] is invalid. Event with id [NotificationTopic] is invalid. No QueueARN or QueueURL provided.", + "errors": [ + { + "errorMessage": "Resource with id [SaveNotificationFunction] is invalid. Event with id [NotificationTopic] is invalid. No QueueARN or QueueURL provided." + } + ] +} \ No newline at end of file diff --git a/tests/translator/output/sns_intrinsics.json b/tests/translator/output/sns_intrinsics.json new file mode 100644 index 0000000000..c591b02664 --- /dev/null +++ b/tests/translator/output/sns_intrinsics.json @@ -0,0 +1,171 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "SnsRegion": { + "Default": "us-east-1", + "Type": "String" + } + }, + "Resources": { + "Notifications": { + "Type": "AWS::SNS::Topic" + }, + "SaveNotificationFunctionNotificationTopicQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "SaveNotificationFunctionNotificationTopicQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "SaveNotificationFunctionNotificationTopicQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + } + }, + "SaveNotificationFunctionNotificationTopic": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "FilterPolicy": { + "Fn::If": [ + "MyCondition", + { + "price_usd": [ + { + "numeric": [ + ">=", + 100 + ] + } + ] + }, + { + "price_usd": [ + { + "numeric": [ + "<", + 100 + ] + } + ] + } + ] + }, + "Region": { + "Ref": "SnsRegion" + }, + "Endpoint": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + } + }, + "SaveNotificationFunctionRole": { + "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", + "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "SaveNotificationFunctionNotificationTopicEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "SaveNotificationFunction" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "SaveNotificationFunctionNotificationTopicQueue", + "Arn" + ] + } + } + }, + "SaveNotificationFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.save_notification", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "notifications.zip" + }, + "Role": { + "Fn::GetAtt": [ + "SaveNotificationFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 0db19f45a8..c0bbdca3c0 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -335,6 +335,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "sns", "sns_sqs", "sns_existing_sqs", + "sns_intrinsics", "sns_outside_sqs", "sns_existing_other_subscription", "sns_topic_outside_template", @@ -723,6 +724,7 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_multiple_resource_errors", "error_null_application_id", "error_s3_not_in_template", + "error_sns_intrinsics", "error_table_invalid_attributetype", "error_table_primary_key_missing_name", "error_table_primary_key_missing_type", From b3a9aba05fd2f6e8811b11b671fa229c3415718c Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Fri, 23 Jul 2021 08:41:59 -0700 Subject: [PATCH 20/28] fix: Validate Trigger field and test Cognito properties with intrinsic functions (#2092) * Add headers whenever cors is set * Fix Cognito trigger validation * Make templates deployable * Removed trigger value validation * Fix Python2 string matching test issue * Use PropertyType mechanism to exclude intrinsics * Remove unused import --- samtranslator/model/eventsources/push.py | 2 +- .../input/cognito_userpool_with_event.yaml | 18 +++-- .../error_cognito_trigger_invalid_type.yaml | 28 ++++++++ .../aws-cn/cognito_userpool_with_event.json | 68 +++++++++++------- .../cognito_userpool_with_event.json | 70 +++++++++++------- .../output/cognito_userpool_with_event.json | 72 +++++++++++-------- .../error_cognito_trigger_invalid_type.json | 8 +++ tests/translator/test_translator.py | 1 + 8 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 tests/translator/input/error_cognito_trigger_invalid_type.yaml create mode 100644 tests/translator/output/error_cognito_trigger_invalid_type.json diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 73b1df299d..8f0f1b804e 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -947,7 +947,7 @@ class Cognito(PushEventSource): property_types = { "UserPool": PropertyType(True, is_str()), - "Trigger": PropertyType(True, one_of(is_str(), list_of(is_str()))), + "Trigger": PropertyType(True, one_of(is_str(), list_of(is_str())), False), } def resources_to_link(self, resources): diff --git a/tests/translator/input/cognito_userpool_with_event.yaml b/tests/translator/input/cognito_userpool_with_event.yaml index 97cf8a5977..bfdd1401a2 100644 --- a/tests/translator/input/cognito_userpool_with_event.yaml +++ b/tests/translator/input/cognito_userpool_with_event.yaml @@ -2,8 +2,16 @@ Resources: UserPool: Type: AWS::Cognito::UserPool Properties: - LambdaConfig: - PreAuthentication: "Test" + UserPoolName: UserPoolName + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + Schema: + - AttributeDataType: String + Name: email + Required: false ImplicitApiFunction: Type: AWS::Serverless::Function Properties: @@ -14,12 +22,12 @@ Resources: OneTrigger: Type: Cognito Properties: - UserPool: + UserPool: Ref: UserPool Trigger: PreSignUp TwoTrigger: Type: Cognito Properties: - UserPool: + UserPool: Ref: UserPool - Trigger: [Test1, Test2] \ No newline at end of file + Trigger: [PostConfirmation, VerifyAuthChallengeResponse] diff --git a/tests/translator/input/error_cognito_trigger_invalid_type.yaml b/tests/translator/input/error_cognito_trigger_invalid_type.yaml new file mode 100644 index 0000000000..0f1fb9d24a --- /dev/null +++ b/tests/translator/input/error_cognito_trigger_invalid_type.yaml @@ -0,0 +1,28 @@ +Resources: + UserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: UserPoolName + Policies: + PasswordPolicy: + MinimumLength: 8 + UsernameAttributes: + - email + Schema: + - AttributeDataType: String + Name: email + Required: false + ImplicitApiFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async () => ‘Hello World!' + Handler: index.handler + Runtime: nodejs12.x + Events: + OneTrigger: + Type: Cognito + Properties: + UserPool: + Ref: UserPool + Trigger: !Join [ "", [ "Pre", "Sign", "Up"] ] diff --git a/tests/translator/output/aws-cn/cognito_userpool_with_event.json b/tests/translator/output/aws-cn/cognito_userpool_with_event.json index 973838a752..9a9faea039 100644 --- a/tests/translator/output/aws-cn/cognito_userpool_with_event.json +++ b/tests/translator/output/aws-cn/cognito_userpool_with_event.json @@ -6,44 +6,60 @@ "LambdaConfig": { "PreSignUp": { "Fn::GetAtt": [ - "ImplicitApiFunction", "Arn" + "ImplicitApiFunction", + "Arn" ] }, - "PreAuthentication": "Test", - "Test1": { + "PostConfirmation": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] }, - "Test2": { + "VerifyAuthChallengeResponse": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] } - } + }, + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ + { + "AttributeDataType": "String", + "Name": "email", + "Required": false + } + ], + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName" } }, "ImplicitApiFunction": { "Type": "AWS::Lambda::Function", "Properties": { - "Handler": "index.gethtml", "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "member_portal.zip" - }, + }, + "Handler": "index.gethtml", "Role": { "Fn::GetAtt": [ - "ImplicitApiFunctionRole", + "ImplicitApiFunctionRole", "Arn" ] - }, + }, "Runtime": "nodejs12.x", "Tags": [ { - "Value": "SAM", - "Key": "lambda:createdBy" + "Key": "lambda:createdBy", + "Value": "SAM" } ] } @@ -51,15 +67,6 @@ "ImplicitApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -75,9 +82,18 @@ } } ] - } + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] } - }, + }, "ImplicitApiFunctionCognitoPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -85,14 +101,14 @@ "FunctionName": { "Ref": "ImplicitApiFunction" }, + "Principal": "cognito-idp.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ "UserPool", "Arn" ] - }, - "Principal": "cognito-idp.amazonaws.com" + } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/cognito_userpool_with_event.json b/tests/translator/output/aws-us-gov/cognito_userpool_with_event.json index 82a1838302..235b0084d1 100644 --- a/tests/translator/output/aws-us-gov/cognito_userpool_with_event.json +++ b/tests/translator/output/aws-us-gov/cognito_userpool_with_event.json @@ -6,44 +6,60 @@ "LambdaConfig": { "PreSignUp": { "Fn::GetAtt": [ - "ImplicitApiFunction", "Arn" + "ImplicitApiFunction", + "Arn" ] }, - "PreAuthentication": "Test", - "Test1": { + "PostConfirmation": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] }, - "Test2": { + "VerifyAuthChallengeResponse": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] } - } + }, + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ + { + "AttributeDataType": "String", + "Name": "email", + "Required": false + } + ], + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName" } }, "ImplicitApiFunction": { - "Type": "AWS::Lambda::Function", + "Type": "AWS::Lambda::Function", "Properties": { - "Handler": "index.gethtml", "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "member_portal.zip" - }, + }, + "Handler": "index.gethtml", "Role": { "Fn::GetAtt": [ - "ImplicitApiFunctionRole", + "ImplicitApiFunctionRole", "Arn" ] - }, + }, "Runtime": "nodejs12.x", "Tags": [ { - "Value": "SAM", - "Key": "lambda:createdBy" + "Key": "lambda:createdBy", + "Value": "SAM" } ] } @@ -51,15 +67,6 @@ "ImplicitApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -75,9 +82,18 @@ } } ] - } + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] } - }, + }, "ImplicitApiFunctionCognitoPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -85,14 +101,14 @@ "FunctionName": { "Ref": "ImplicitApiFunction" }, + "Principal": "cognito-idp.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ "UserPool", "Arn" ] - }, - "Principal": "cognito-idp.amazonaws.com" + } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/cognito_userpool_with_event.json b/tests/translator/output/cognito_userpool_with_event.json index 277f38c37e..956742b7f2 100644 --- a/tests/translator/output/cognito_userpool_with_event.json +++ b/tests/translator/output/cognito_userpool_with_event.json @@ -6,44 +6,60 @@ "LambdaConfig": { "PreSignUp": { "Fn::GetAtt": [ - "ImplicitApiFunction", "Arn" + "ImplicitApiFunction", + "Arn" ] }, - "PreAuthentication": "Test", - "Test1": { + "PostConfirmation": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] }, - "Test2": { + "VerifyAuthChallengeResponse": { "Fn::GetAtt": [ "ImplicitApiFunction", "Arn" ] - } - } + } + }, + "Policies": { + "PasswordPolicy": { + "MinimumLength": 8 + } + }, + "Schema": [ + { + "AttributeDataType": "String", + "Name": "email", + "Required": false + } + ], + "UsernameAttributes": [ + "email" + ], + "UserPoolName": "UserPoolName" } }, "ImplicitApiFunction": { - "Type": "AWS::Lambda::Function", + "Type": "AWS::Lambda::Function", "Properties": { - "Handler": "index.gethtml", "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "member_portal.zip" - }, + }, + "Handler": "index.gethtml", "Role": { "Fn::GetAtt": [ - "ImplicitApiFunctionRole", + "ImplicitApiFunctionRole", "Arn" ] - }, + }, "Runtime": "nodejs12.x", "Tags": [ { - "Value": "SAM", - "Key": "lambda:createdBy" + "Key": "lambda:createdBy", + "Value": "SAM" } ] } @@ -51,15 +67,6 @@ "ImplicitApiFunctionRole": { "Type": "AWS::IAM::Role", "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], "AssumeRolePolicyDocument": { "Version": "2012-10-17", "Statement": [ @@ -75,9 +82,18 @@ } } ] - } + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] } - }, + }, "ImplicitApiFunctionCognitoPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -85,14 +101,14 @@ "FunctionName": { "Ref": "ImplicitApiFunction" }, + "Principal": "cognito-idp.amazonaws.com", "SourceArn": { "Fn::GetAtt": [ "UserPool", "Arn" ] - }, - "Principal": "cognito-idp.amazonaws.com" + } } } } -} +} \ No newline at end of file diff --git a/tests/translator/output/error_cognito_trigger_invalid_type.json b/tests/translator/output/error_cognito_trigger_invalid_type.json new file mode 100644 index 0000000000..1a9bbdd385 --- /dev/null +++ b/tests/translator/output/error_cognito_trigger_invalid_type.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [ImplicitApiFunctionOneTrigger] is invalid. Type of property 'Trigger' is invalid." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ImplicitApiFunctionOneTrigger] is invalid. Type of property 'Trigger' is invalid." +} diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index c0bbdca3c0..e96e4689ef 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -666,6 +666,7 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_state_machine_with_cwe_invalid_dlq_type", "error_state_machine_with_cwe_both_dlq_property_provided", "error_state_machine_with_cwe_missing_dlq_property", + "error_cognito_trigger_invalid_type", "error_cognito_userpool_duplicate_trigger", "error_cognito_userpool_not_string", "error_api_duplicate_methods_same_path", From 5a4a81b1b65750834965e3f27fd393cfa8902af9 Mon Sep 17 00:00:00 2001 From: Cosh_ Date: Fri, 23 Jul 2021 14:20:47 -0700 Subject: [PATCH 21/28] feat(Intrinsic Tests): Added Tests for Kinesis Event Source with Intrinsics (#2103) * Added Tests for Kinesis EventSource with Intrinsics * Fixed Formatting with Black --- .../combination/test_function_with_kinesis.py | 23 +++ .../function_with_kinesis_intrinsics.json | 6 + .../function_with_kinesis_intrinsics.yaml | 82 +++++++++ .../translator/input/kinesis_intrinsics.yaml | 82 +++++++++ .../output/aws-cn/kinesis_intrinsics.json | 168 ++++++++++++++++++ .../output/aws-us-gov/kinesis_intrinsics.json | 168 ++++++++++++++++++ .../translator/output/kinesis_intrinsics.json | 168 ++++++++++++++++++ tests/translator/test_translator.py | 1 + 8 files changed, 698 insertions(+) create mode 100644 integration/resources/expected/combination/function_with_kinesis_intrinsics.json create mode 100644 integration/resources/templates/combination/function_with_kinesis_intrinsics.yaml create mode 100644 tests/translator/input/kinesis_intrinsics.yaml create mode 100644 tests/translator/output/aws-cn/kinesis_intrinsics.json create mode 100644 tests/translator/output/aws-us-gov/kinesis_intrinsics.json create mode 100644 tests/translator/output/kinesis_intrinsics.json diff --git a/integration/combination/test_function_with_kinesis.py b/integration/combination/test_function_with_kinesis.py index 16f0adc5d4..2e5af72aa4 100644 --- a/integration/combination/test_function_with_kinesis.py +++ b/integration/combination/test_function_with_kinesis.py @@ -22,3 +22,26 @@ def test_function_with_kinesis_trigger(self): self.assertEqual(event_source_mapping_batch_size, 100) self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) self.assertEqual(event_source_mapping_kinesis_stream_arn, kinesis_stream["StreamARN"]) + + +class TestFunctionWithKinesisIntrinsics(BaseTest): + def test_function_with_kinesis_trigger(self): + self.create_and_verify_stack("combination/function_with_kinesis_intrinsics") + + kinesis_client = self.client_provider.kinesis_client + kinesis_id = self.get_physical_id_by_type("AWS::Kinesis::Stream") + kinesis_stream = kinesis_client.describe_stream(StreamName=kinesis_id)["StreamDescription"] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_arn = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_arn) + event_source_mapping_batch_size = event_source_mapping_result["BatchSize"] + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_kinesis_stream_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_batch_size, 100) + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_kinesis_stream_arn, kinesis_stream["StreamARN"]) diff --git a/integration/resources/expected/combination/function_with_kinesis_intrinsics.json b/integration/resources/expected/combination/function_with_kinesis_intrinsics.json new file mode 100644 index 0000000000..64511cfc40 --- /dev/null +++ b/integration/resources/expected/combination/function_with_kinesis_intrinsics.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyStream", "ResourceType":"AWS::Kinesis::Stream" }, + { "LogicalResourceId":"MyLambdaFunctionKinesisStream", "ResourceType":"AWS::Lambda::EventSourceMapping" } +] \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_kinesis_intrinsics.yaml b/integration/resources/templates/combination/function_with_kinesis_intrinsics.yaml new file mode 100644 index 0000000000..8dbd0b8b13 --- /dev/null +++ b/integration/resources/templates/combination/function_with_kinesis_intrinsics.yaml @@ -0,0 +1,82 @@ +Parameters: + IntValue: + Type: Number + Default: 100 + + One: + Type: Number + Default: 1 + + StartingPositionValue: + Type: String + Default: LATEST + + FunctionResponseTypesValue: + Type: String + Default: ReportBatchItemFailures + +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + KinesisStream: + Type: Kinesis + Properties: + BatchSize: + Ref: IntValue + BisectBatchOnFunctionError: + Fn::If: + - FalseCondition + - True + - False + Enabled: + Fn::If: + - TrueCondition + - True + - False + FunctionResponseTypes: + - Ref: FunctionResponseTypesValue + MaximumBatchingWindowInSeconds: + Ref: One + MaximumRecordAgeInSeconds: + Ref: IntValue + MaximumRetryAttempts: + Ref: One + ParallelizationFactor: + Ref: One + StartingPosition: + Ref: StartingPositionValue + Stream: + # Connect with the stream we have created in this template + Fn::Join: + - '' + - - 'arn:' + - Ref: AWS::Partition + - ':kinesis:' + - Ref: AWS::Region + - ':' + - Ref: AWS::AccountId + - ':stream/' + - Ref: MyStream + TumblingWindowInSeconds: + Ref: IntValue + MyStream: + Type: AWS::Kinesis::Stream + Properties: + ShardCount: 1 diff --git a/tests/translator/input/kinesis_intrinsics.yaml b/tests/translator/input/kinesis_intrinsics.yaml new file mode 100644 index 0000000000..7107cb06ad --- /dev/null +++ b/tests/translator/input/kinesis_intrinsics.yaml @@ -0,0 +1,82 @@ +Parameters: + IntValue: + Type: Number + Default: 50 + + StringValue: + Type: String + Default: us-east-1 + + StartingPositionValue: + Type: String + Default: LATEST + + FunctionResponseTypesValue: + Type: String + Default: ReportBatchItemFailures + +Conditions: + TrueCondition: + Fn::Equals: + - true + - true + FalseCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: s3://sam-demo-bucket/stream.zip + MemorySize: 128 + + Events: + KinesisStream: + Type: Kinesis + Properties: + BatchSize: + Ref: IntValue + BisectBatchOnFunctionError: + Fn::If: + - FalseCondition + - True + - False + Enabled: + Fn::If: + - TrueCondition + - True + - False + FunctionResponseTypes: + - Ref: FunctionResponseTypesValue + MaximumBatchingWindowInSeconds: + Ref: IntValue + MaximumRecordAgeInSeconds: + Ref: IntValue + MaximumRetryAttempts: + Ref: IntValue + ParallelizationFactor: + Ref: IntValue + StartingPosition: + Ref: StartingPositionValue + Stream: + # Connect with the stream we have created in this template + Fn::Join: + - '' + - - 'arn:' + - Ref: AWS::Partition + - ':kinesis:' + - Ref: AWS::Region + - ':' + - Ref: AWS::AccountId + - ':stream/' + - Ref: MyStream + TumblingWindowInSeconds: + Ref: IntValue + MyStream: + Type: AWS::Kinesis::Stream + Properties: + ShardCount: 1 diff --git a/tests/translator/output/aws-cn/kinesis_intrinsics.json b/tests/translator/output/aws-cn/kinesis_intrinsics.json new file mode 100644 index 0000000000..0484d1fdcc --- /dev/null +++ b/tests/translator/output/aws-cn/kinesis_intrinsics.json @@ -0,0 +1,168 @@ +{ + "Conditions": { + "TrueCondition": { + "Fn::Equals": [ + true, + true + ] + }, + "FalseCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Resources": { + "MyLambdaFunctionRole": { + "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", + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyLambdaFunctionKinesisStream": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "MaximumBatchingWindowInSeconds": { + "Ref": "IntValue" + }, + "FunctionName": { + "Ref": "MyLambdaFunction" + }, + "MaximumRecordAgeInSeconds": { + "Ref": "IntValue" + }, + "FunctionResponseTypes": [ + { + "Ref": "FunctionResponseTypesValue" + } + ], + "BatchSize": { + "Ref": "IntValue" + }, + "TumblingWindowInSeconds": { + "Ref": "IntValue" + }, + "Enabled": { + "Fn::If": [ + "TrueCondition", + true, + false + ] + }, + "EventSourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kinesis:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stream/", + { + "Ref": "MyStream" + } + ] + ] + }, + "StartingPosition": { + "Ref": "StartingPositionValue" + }, + "ParallelizationFactor": { + "Ref": "IntValue" + }, + "MaximumRetryAttempts": { + "Ref": "IntValue" + }, + "BisectBatchOnFunctionError": { + "Fn::If": [ + "FalseCondition", + true, + false + ] + } + } + }, + "MyStream": { + "Type": "AWS::Kinesis::Stream", + "Properties": { + "ShardCount": 1 + } + }, + "MyLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "stream.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 128, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyLambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + } + }, + "Parameters": { + "StringValue": { + "Default": "us-east-1", + "Type": "String" + }, + "IntValue": { + "Default": 50, + "Type": "Number" + }, + "FunctionResponseTypesValue": { + "Default": "ReportBatchItemFailures", + "Type": "String" + }, + "StartingPositionValue": { + "Default": "LATEST", + "Type": "String" + } + } + } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/kinesis_intrinsics.json b/tests/translator/output/aws-us-gov/kinesis_intrinsics.json new file mode 100644 index 0000000000..a3490c2797 --- /dev/null +++ b/tests/translator/output/aws-us-gov/kinesis_intrinsics.json @@ -0,0 +1,168 @@ +{ + "Conditions": { + "TrueCondition": { + "Fn::Equals": [ + true, + true + ] + }, + "FalseCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Resources": { + "MyLambdaFunctionRole": { + "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", + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyLambdaFunctionKinesisStream": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "MaximumBatchingWindowInSeconds": { + "Ref": "IntValue" + }, + "FunctionName": { + "Ref": "MyLambdaFunction" + }, + "MaximumRecordAgeInSeconds": { + "Ref": "IntValue" + }, + "FunctionResponseTypes": [ + { + "Ref": "FunctionResponseTypesValue" + } + ], + "BatchSize": { + "Ref": "IntValue" + }, + "TumblingWindowInSeconds": { + "Ref": "IntValue" + }, + "Enabled": { + "Fn::If": [ + "TrueCondition", + true, + false + ] + }, + "EventSourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kinesis:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stream/", + { + "Ref": "MyStream" + } + ] + ] + }, + "StartingPosition": { + "Ref": "StartingPositionValue" + }, + "ParallelizationFactor": { + "Ref": "IntValue" + }, + "MaximumRetryAttempts": { + "Ref": "IntValue" + }, + "BisectBatchOnFunctionError": { + "Fn::If": [ + "FalseCondition", + true, + false + ] + } + } + }, + "MyStream": { + "Type": "AWS::Kinesis::Stream", + "Properties": { + "ShardCount": 1 + } + }, + "MyLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "stream.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 128, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyLambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + } + }, + "Parameters": { + "StringValue": { + "Default": "us-east-1", + "Type": "String" + }, + "IntValue": { + "Default": 50, + "Type": "Number" + }, + "FunctionResponseTypesValue": { + "Default": "ReportBatchItemFailures", + "Type": "String" + }, + "StartingPositionValue": { + "Default": "LATEST", + "Type": "String" + } + } + } \ No newline at end of file diff --git a/tests/translator/output/kinesis_intrinsics.json b/tests/translator/output/kinesis_intrinsics.json new file mode 100644 index 0000000000..c7802de792 --- /dev/null +++ b/tests/translator/output/kinesis_intrinsics.json @@ -0,0 +1,168 @@ +{ + "Conditions": { + "TrueCondition": { + "Fn::Equals": [ + true, + true + ] + }, + "FalseCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Resources": { + "MyLambdaFunctionRole": { + "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", + "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyLambdaFunctionKinesisStream": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "MaximumBatchingWindowInSeconds": { + "Ref": "IntValue" + }, + "FunctionName": { + "Ref": "MyLambdaFunction" + }, + "MaximumRecordAgeInSeconds": { + "Ref": "IntValue" + }, + "FunctionResponseTypes": [ + { + "Ref": "FunctionResponseTypesValue" + } + ], + "BatchSize": { + "Ref": "IntValue" + }, + "TumblingWindowInSeconds": { + "Ref": "IntValue" + }, + "Enabled": { + "Fn::If": [ + "TrueCondition", + true, + false + ] + }, + "EventSourceArn": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":kinesis:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":stream/", + { + "Ref": "MyStream" + } + ] + ] + }, + "StartingPosition": { + "Ref": "StartingPositionValue" + }, + "ParallelizationFactor": { + "Ref": "IntValue" + }, + "MaximumRetryAttempts": { + "Ref": "IntValue" + }, + "BisectBatchOnFunctionError": { + "Fn::If": [ + "FalseCondition", + true, + false + ] + } + } + }, + "MyStream": { + "Type": "AWS::Kinesis::Stream", + "Properties": { + "ShardCount": 1 + } + }, + "MyLambdaFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "stream.zip" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 128, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyLambdaFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + } + }, + "Parameters": { + "StringValue": { + "Default": "us-east-1", + "Type": "String" + }, + "IntValue": { + "Default": 50, + "Type": "Number" + }, + "FunctionResponseTypesValue": { + "Default": "ReportBatchItemFailures", + "Type": "String" + }, + "StartingPositionValue": { + "Default": "LATEST", + "Type": "String" + } + } + } \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index e96e4689ef..93b8c41bfe 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -342,6 +342,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "alexa_skill", "alexa_skill_with_skill_id", "iot_rule", + "kinesis_intrinsics", "layers_with_intrinsics", "layers_all_properties", "layer_deletion_policy_precedence", From 938414a63d622f43523205256a1f2823964589e3 Mon Sep 17 00:00:00 2001 From: Ahmed Elbayaa <72949274+elbayaaa@users.noreply.github.com> Date: Sun, 25 Jul 2021 19:09:50 -0700 Subject: [PATCH 22/28] fix: Don't attempt to refetch a swagger method object by its name as we already have it (#2031) * Fix a bad logic where a hash key is modified then refetched which results in KeyError * add unit tests and update other instance of same usage on line 534 * fix: Fix the same code in open_api.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Mathieu Grandis --- samtranslator/open_api/open_api.py | 2 +- samtranslator/swagger/swagger.py | 7 +- .../input/api_with_any_method_in_swagger.yaml | 44 ++++ .../api_with_any_method_in_swagger.json | 189 +++++++++++++++++ .../api_with_any_method_in_swagger.json | 197 ++++++++++++++++++ .../api_with_any_method_in_swagger.json | 197 ++++++++++++++++++ tests/translator/test_translator.py | 1 + 7 files changed, 632 insertions(+), 5 deletions(-) create mode 100644 tests/translator/input/api_with_any_method_in_swagger.yaml create mode 100644 tests/translator/output/api_with_any_method_in_swagger.json create mode 100644 tests/translator/output/aws-cn/api_with_any_method_in_swagger.json create mode 100644 tests/translator/output/aws-us-gov/api_with_any_method_in_swagger.json diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index af6b47e49c..54e00ed05d 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -352,7 +352,7 @@ def set_path_default_authorizer(self, path, default_authorizer, authorizers, api ) ] ) - for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + for method_definition in self.get_method_contents(method): # If no integration given, then we don't need to process this definition (could be AWS::NoValue) if not self.method_definition_has_integration(method_definition): continue diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 33565be6ea..c5c80ee382 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -533,7 +533,7 @@ def set_path_default_authorizer( if add_default_auth_to_preflight or normalized_method_name != "options": normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. - for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + for method_definition in self.get_method_contents(method): # If no integration given, then we don't need to process this definition (could be AWS::NoValue) if not isinstance(method_definition, dict): @@ -622,14 +622,13 @@ def set_path_default_apikey_required(self, path): :param string path: Path name """ - for method_name, _ in self.get_path(path).items(): + for method_name, method in self.get_path(path).items(): # Excluding parameters section if method_name == "parameters": continue - normalized_method_name = self._normalize_method_name(method_name) # It is possible that the method could have two definitions in a Fn::If block. - for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]): + for method_definition in self.get_method_contents(method): # If no integration given, then we don't need to process this definition (could be AWS::NoValue) if not self.method_definition_has_integration(method_definition): diff --git a/tests/translator/input/api_with_any_method_in_swagger.yaml b/tests/translator/input/api_with_any_method_in_swagger.yaml new file mode 100644 index 0000000000..bd6b304744 --- /dev/null +++ b/tests/translator/input/api_with_any_method_in_swagger.yaml @@ -0,0 +1,44 @@ +Resources: + HttpApiFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/todo_list.zip + Handler: index.restapi + Runtime: python3.7 + Events: + SimpleCase: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + BasePath: + Type: HttpApi + Properties: + ApiId: !Ref MyApi + Path: / + Method: get + + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: + Ref: Stage + Auth: + DefaultAuthorizer: "LambdaAuthorizer" + Authorizers: + LambdaAuthorizer: + FunctionPayloadType: "REQUEST" + Identity: + Headers: + - "Authorization" + DefinitionBody: + openapi: '3.0' + info: + title: !Sub ${AWS::StackName}-Api + paths: + /: + any: + 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/output/api_with_any_method_in_swagger.json b/tests/translator/output/api_with_any_method_in_swagger.json new file mode 100644 index 0000000000..c4f81f32b2 --- /dev/null +++ b/tests/translator/output/api_with_any_method_in_swagger.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "title": { + "Fn::Sub": "${AWS::StackName}-Api" + } + }, + "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, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + }, + "/": { + "any": { + "x-amazon-apigateway-integration": { + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ] + }, + "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" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "securitySchemes": { + "LambdaAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": null + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "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" + } + } + ] + } + } + }, + "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" + } + ] + } + }, + "MyApiStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment69a80e7382" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": { + "Ref": "Stage" + } + } + }, + "MyApiDeployment69a80e7382": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 69a80e738222706cff079ab8d7f348c0d89eddab", + "StageName": "Stage" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_any_method_in_swagger.json b/tests/translator/output/aws-cn/api_with_any_method_in_swagger.json new file mode 100644 index 0000000000..44e182380e --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_any_method_in_swagger.json @@ -0,0 +1,197 @@ +{ + "Resources": { + "MyApiStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment37c803d096" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": { + "Ref": "Stage" + } + } + }, + "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" + } + ] + } + }, + "MyApiDeployment37c803d096": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 37c803d09628334a047966047f087abf49267a2c", + "StageName": "Stage" + } + }, + "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" + } + } + ] + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "title": { + "Fn::Sub": "${AWS::StackName}-Api" + } + }, + "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, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + }, + "/": { + "any": { + "x-amazon-apigateway-integration": { + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ] + }, + "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" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "securitySchemes": { + "LambdaAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": null + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_any_method_in_swagger.json b/tests/translator/output/aws-us-gov/api_with_any_method_in_swagger.json new file mode 100644 index 0000000000..9bd62ca944 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_any_method_in_swagger.json @@ -0,0 +1,197 @@ +{ + "Resources": { + "MyApiStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment0ebc8893a3" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": { + "Ref": "Stage" + } + } + }, + "HttpApiFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.restapi", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "todo_list.zip" + }, + "Role": { + "Fn::GetAtt": [ + "HttpApiFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.7", + "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" + } + } + ] + } + } + }, + "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" + } + ] + } + }, + "MyApiDeployment0ebc8893a3": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 0ebc8893a30055fb944d26720dc8ffddacc6d27d", + "StageName": "Stage" + } + }, + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "title": { + "Fn::Sub": "${AWS::StackName}-Api" + } + }, + "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, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + }, + "/": { + "any": { + "x-amazon-apigateway-integration": { + "httpMethod": "ANY", + "type": "http_proxy", + "uri": "https://www.alphavantage.co/", + "payloadFormatVersion": "1.0" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ] + }, + "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" + }, + "security": [ + { + "LambdaAuthorizer": [] + } + ], + "responses": {} + } + } + }, + "openapi": "3.0", + "components": { + "securitySchemes": { + "LambdaAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": null + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 93b8c41bfe..44eaa17c5d 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -318,6 +318,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "api_with_gateway_responses_string_status_code", "api_cache", "api_with_access_log_setting", + "api_with_any_method_in_swagger", "api_with_canary_setting", "api_with_xray_tracing", "api_request_model", From c01321d41a53da570b0c3ddd619ffbd6c237ed5d Mon Sep 17 00:00:00 2001 From: Mathieu Grandis <73313235+mgrandis@users.noreply.github.com> Date: Mon, 26 Jul 2021 14:30:15 -0700 Subject: [PATCH 23/28] test: Adding intrinsic tests for Function S3 Event (#2100) --- .../test_function_with_s3_bucket.py | 16 ++- .../function_with_s3_intrinsics.json | 18 +++ .../function_with_s3_intrinsics.yaml | 36 +++++ samtranslator/model/eventsources/push.py | 2 +- .../error_function_invalid_s3_event.yaml | 23 ++++ tests/translator/input/s3_intrinsics.yaml | 40 ++++++ .../output/aws-cn/s3_intrinsics.json | 130 ++++++++++++++++++ .../output/aws-us-gov/s3_intrinsics.json | 130 ++++++++++++++++++ .../error_function_invalid_s3_event.json | 8 ++ tests/translator/output/s3_intrinsics.json | 130 ++++++++++++++++++ tests/translator/test_translator.py | 2 + 11 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 integration/resources/expected/combination/function_with_s3_intrinsics.json create mode 100644 integration/resources/templates/combination/function_with_s3_intrinsics.yaml create mode 100644 tests/translator/input/error_function_invalid_s3_event.yaml create mode 100644 tests/translator/input/s3_intrinsics.yaml create mode 100644 tests/translator/output/aws-cn/s3_intrinsics.json create mode 100644 tests/translator/output/aws-us-gov/s3_intrinsics.json create mode 100644 tests/translator/output/error_function_invalid_s3_event.json create mode 100644 tests/translator/output/s3_intrinsics.json diff --git a/integration/combination/test_function_with_s3_bucket.py b/integration/combination/test_function_with_s3_bucket.py index 8e04d41d23..90ddb3016d 100644 --- a/integration/combination/test_function_with_s3_bucket.py +++ b/integration/combination/test_function_with_s3_bucket.py @@ -15,4 +15,18 @@ def test_function_with_s3_bucket_trigger(self): # There should be only One notification configuration for the event self.assertEqual(len(configurations), 1) config = configurations[0] - self.assertEqual(set(config["Events"]), {"s3:ObjectCreated:*"}) + self.assertEqual(config["Events"], ["s3:ObjectCreated:*"]) + + def test_function_with_s3_bucket_intrinsics(self): + self.create_and_verify_stack("combination/function_with_s3_intrinsics") + + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + + self.assertEqual(len(configurations), 1) + config = configurations[0] + self.assertEqual(config["Events"], ["s3:ObjectCreated:*"]) + self.assertEqual(config["Filter"]["Key"]["FilterRules"], [{"Name": "Suffix", "Value": "object_suffix"}]) diff --git a/integration/resources/expected/combination/function_with_s3_intrinsics.json b/integration/resources/expected/combination/function_with_s3_intrinsics.json new file mode 100644 index 0000000000..2fdb0626a4 --- /dev/null +++ b/integration/resources/expected/combination/function_with_s3_intrinsics.json @@ -0,0 +1,18 @@ +[ + { + "LogicalResourceId": "MyLambdaFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyLambdaFunctionRole", + "ResourceType": "AWS::IAM::Role" + }, + { + "LogicalResourceId": "MyLambdaFunctionS3EventPermission", + "ResourceType": "AWS::Lambda::Permission" + }, + { + "LogicalResourceId": "MyBucket", + "ResourceType": "AWS::S3::Bucket" + } +] \ No newline at end of file diff --git a/integration/resources/templates/combination/function_with_s3_intrinsics.yaml b/integration/resources/templates/combination/function_with_s3_intrinsics.yaml new file mode 100644 index 0000000000..8de86f083b --- /dev/null +++ b/integration/resources/templates/combination/function_with_s3_intrinsics.yaml @@ -0,0 +1,36 @@ +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: MyBucket + Events: s3:ObjectCreated:* + Filter: + Fn::If: + - MyCondition + - S3Key: + Rules: + - Name: prefix + Value: object_prefix + - S3Key: + Rules: + - Name: suffix + Value: object_suffix + + MyBucket: + Type: AWS::S3::Bucket diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 8f0f1b804e..5396894258 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -244,7 +244,7 @@ class S3(PushEventSource): principal = "s3.amazonaws.com" property_types = { "Bucket": PropertyType(True, is_str()), - "Events": PropertyType(True, one_of(is_str(), list_of(is_str()))), + "Events": PropertyType(True, one_of(is_str(), list_of(is_str())), False), "Filter": PropertyType(False, dict_of(is_str(), is_str())), } diff --git a/tests/translator/input/error_function_invalid_s3_event.yaml b/tests/translator/input/error_function_invalid_s3_event.yaml new file mode 100644 index 0000000000..3fd48f82ac --- /dev/null +++ b/tests/translator/input/error_function_invalid_s3_event.yaml @@ -0,0 +1,23 @@ +Parameters: + EventsParam: + Type: String + Default: s3:ObjectCreated:* + +Resources: + FunctionInvalidS3Event: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python2.7 + Events: + S3Event: + Type: S3 + Properties: + Bucket: + Ref: MyBucket + Events: + Ref: EventsParam + + MyBucket: + Type: AWS::S3::Bucket diff --git a/tests/translator/input/s3_intrinsics.yaml b/tests/translator/input/s3_intrinsics.yaml new file mode 100644 index 0000000000..dff8fe9802 --- /dev/null +++ b/tests/translator/input/s3_intrinsics.yaml @@ -0,0 +1,40 @@ +Parameters: + EventsParam: + Type: String + Default: s3:ObjectCreated:* + +Conditions: + MyCondition: + Fn::Equals: + - true + - false + +Resources: + ThumbnailFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/thumbnails.zip + Handler: index.generate_thumbails + Runtime: nodejs12.x + Events: + ImageBucket: + Type: S3 + Properties: + Bucket: + Ref: Images + Events: + - s3:ObjectCreated:* + Filter: + Fn::If: + - MyCondition + - S3Key: + Rules: + - Name: Rule1Prefix + Value: Rule1Value + - S3Key: + Rules: + - Name: Rule2Prefix + Value: Rule2Value + + Images: + Type: AWS::S3::Bucket diff --git a/tests/translator/output/aws-cn/s3_intrinsics.json b/tests/translator/output/aws-cn/s3_intrinsics.json new file mode 100644 index 0000000000..834251134c --- /dev/null +++ b/tests/translator/output/aws-cn/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/s3_intrinsics.json b/tests/translator/output/aws-us-gov/s3_intrinsics.json new file mode 100644 index 0000000000..947ec4ef8b --- /dev/null +++ b/tests/translator/output/aws-us-gov/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_invalid_s3_event.json b/tests/translator/output/error_function_invalid_s3_event.json new file mode 100644 index 0000000000..d762590732 --- /dev/null +++ b/tests/translator/output/error_function_invalid_s3_event.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionInvalidS3EventS3Event] is invalid. Type of property 'Events' is invalid." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [FunctionInvalidS3EventS3Event] is invalid. Type of property 'Events' is invalid." +} \ No newline at end of file diff --git a/tests/translator/output/s3_intrinsics.json b/tests/translator/output/s3_intrinsics.json new file mode 100644 index 0000000000..6b5e52f3e3 --- /dev/null +++ b/tests/translator/output/s3_intrinsics.json @@ -0,0 +1,130 @@ +{ + "Conditions": { + "MyCondition": { + "Fn::Equals": [ + true, + false + ] + } + }, + "Parameters": { + "EventsParam": { + "Default": "s3:ObjectCreated:*", + "Type": "String" + } + }, + "Resources": { + "Images": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "LambdaConfigurations": [ + { + "Function": { + "Fn::GetAtt": [ + "ThumbnailFunction", + "Arn" + ] + }, + "Filter": { + "Fn::If": [ + "MyCondition", + { + "S3Key": { + "Rules": [ + { + "Name": "Rule1Prefix", + "Value": "Rule1Value" + } + ] + } + }, + { + "S3Key": { + "Rules": [ + { + "Name": "Rule2Prefix", + "Value": "Rule2Value" + } + ] + } + } + ] + }, + "Event": "s3:ObjectCreated:*" + } + ] + } + }, + "DependsOn": [ + "ThumbnailFunctionImageBucketPermission" + ] + }, + "ThumbnailFunctionRole": { + "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" + } + ] + } + }, + "ThumbnailFunctionImageBucketPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "SourceAccount": { + "Ref": "AWS::AccountId" + }, + "FunctionName": { + "Ref": "ThumbnailFunction" + }, + "Principal": "s3.amazonaws.com" + } + }, + "ThumbnailFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.generate_thumbails", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Role": { + "Fn::GetAtt": [ + "ThumbnailFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 44eaa17c5d..03339c30e2 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -330,6 +330,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "s3_existing_lambda_notification_configuration", "s3_existing_other_notification_configuration", "s3_filter", + "s3_intrinsics", "s3_multiple_events_same_bucket", "s3_multiple_functions", "s3_with_dependsOn", @@ -697,6 +698,7 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_cors_credentials_true_without_explicit_origin", "error_function_invalid_codeuri", "error_function_invalid_api_event", + "error_function_invalid_s3_event", "error_function_invalid_autopublishalias", "error_function_invalid_event_type", "error_function_invalid_layer", From e3057b3f8b1997c0e18cf5bffa8415887f632f4a Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Tue, 27 Jul 2021 11:40:21 -0500 Subject: [PATCH 24/28] fix(bug): Check if Identity.ReauthorizeEvery equals zero (#2105) * Revert "Revert "Issue 1508 remove check requiring identity ... (#1577)" (#2038)" This reverts commit ed3c28335aedce127f2b2738788b61851de62700. * Update implementation to support intrinsics/ add more tests to validate changes * Fixing hashes for py2 * Run make black * Handle pr feedback * Add another unit tests to cover the original issue Co-authored-by: Jacob Fuss --- samtranslator/model/apigateway.py | 18 +- tests/model/test_api.py | 98 ++- .../input/api_with_auth_all_minimum.yaml | 21 + .../input/api_with_identity_intrinsic.yaml | 22 + .../output/api_with_auth_all_minimum.json | 766 ++++++++++------- .../output/api_with_identity_intrinsic.json | 90 ++ .../aws-cn/api_with_auth_all_minimum.json | 782 ++++++++++------- .../aws-cn/api_with_identity_intrinsic.json | 98 +++ .../aws-us-gov/api_with_auth_all_minimum.json | 804 ++++++++++-------- .../api_with_identity_intrinsic.json | 98 +++ tests/translator/test_translator.py | 1 + 11 files changed, 1810 insertions(+), 988 deletions(-) create mode 100644 tests/translator/input/api_with_identity_intrinsic.yaml create mode 100644 tests/translator/output/api_with_identity_intrinsic.json create mode 100644 tests/translator/output/aws-cn/api_with_identity_intrinsic.json create mode 100644 tests/translator/output/aws-us-gov/api_with_identity_intrinsic.json diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index d1ea0c2b1d..428ac1972f 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -270,11 +270,19 @@ def _is_missing_identity_source(self, identity): query_strings = identity.get("QueryStrings") stage_variables = identity.get("StageVariables") context = identity.get("Context") + ttl = identity.get("ReauthorizeEvery") - if not headers and not query_strings and not stage_variables and not context: - return True + required_properties_missing = not headers and not query_strings and not stage_variables and not context + + try: + ttl_int = int(ttl) + # this will catch if ttl is None and not convertable to an int + except TypeError: + # previous behavior before trying to read ttl + return required_properties_missing - return False + # If we can resolve ttl, attempt to see if things are valid + return ttl_int > 0 and required_properties_missing def generate_swagger(self): authorizer_type = self._get_type() @@ -314,7 +322,9 @@ def generate_swagger(self): swagger[APIGATEWAY_AUTHORIZER_KEY]["authorizerCredentials"] = function_invoke_role if self._get_function_payload_type() == "REQUEST": - swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source() + identity_source = self._get_identity_source() + if identity_source: + swagger[APIGATEWAY_AUTHORIZER_KEY]["identitySource"] = self._get_identity_source() # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN is_lambda_token_authorizer = authorizer_type == "LAMBDA" and self._get_function_payload_type() == "TOKEN" diff --git a/tests/model/test_api.py b/tests/model/test_api.py index 627bda3d5c..708b83a5a5 100644 --- a/tests/model/test_api.py +++ b/tests/model/test_api.py @@ -14,6 +14,100 @@ def test_create_oauth2_auth(self): def test_create_authorizer_fails_with_string_authorization_scopes(self): with pytest.raises(InvalidResourceException): - auth = ApiGatewayAuthorizer( - api_logical_id="logicalId", name="authName", authorization_scopes="invalid_scope" + ApiGatewayAuthorizer(api_logical_id="logicalId", name="authName", authorization_scopes="invalid_scope") + + def test_create_authorizer_fails_with_missing_identity_values_and_not_cached(self): + with pytest.raises(InvalidResourceException): + ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": 10}, + function_payload_type="REQUEST", + ) + + def test_create_authorizer_fails_with_empty_identity(self): + with pytest.raises(InvalidResourceException): + ApiGatewayAuthorizer( + api_logical_id="logicalId", name="authName", identity={}, function_payload_type="REQUEST" ) + + def test_create_authorizer_doesnt_fail_with_identity_reauthorization_every_as_zero(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": 0}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_non_integer_identity(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": [], "Headers": ["Accept"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_identity_intrinsic_is_valid_with_headers(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": {"FN:If": ["isProd", 10, 0]}, "Headers": ["Accept"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_identity_intrinsic_is_invalid_if_no_querystring_stagevariables_context_headers( + self, + ): + with pytest.raises(InvalidResourceException): + ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": {"FN:If": ["isProd", 10, 0]}}, + function_payload_type="REQUEST", + ) + + def test_create_authorizer_with_identity_intrinsic_is_valid_with_context(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": {"FN:If": ["isProd", 10, 0]}, "Context": ["Accept"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_identity_intrinsic_is_valid_with_stage_variables(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": {"FN:If": ["isProd", 10, 0]}, "StageVariables": ["Stage"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_identity_intrinsic_is_valid_with_query_strings(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": {"FN:If": ["isProd", 10, 0]}, "QueryStrings": ["AQueryString"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) + + def test_create_authorizer_with_identity_ReauthorizeEvery_asNone_valid_with_query_strings(self): + auth = ApiGatewayAuthorizer( + api_logical_id="logicalId", + name="authName", + identity={"ReauthorizeEvery": None, "QueryStrings": ["AQueryString"]}, + function_payload_type="REQUEST", + ) + + self.assertIsNotNone(auth) diff --git a/tests/translator/input/api_with_auth_all_minimum.yaml b/tests/translator/input/api_with_auth_all_minimum.yaml index 399df76126..5066b20d9b 100644 --- a/tests/translator/input/api_with_auth_all_minimum.yaml +++ b/tests/translator/input/api_with_auth_all_minimum.yaml @@ -32,6 +32,20 @@ Resources: Identity: Headers: - Authorization1 + + MyApiWithNotCachedLambdaRequestAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaRequestAuth + Authorizers: + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFn.Arn + Identity: + ReauthorizeEvery: 0 + MyAuthFn: Type: AWS::Serverless::Function Properties: @@ -81,6 +95,13 @@ Resources: RestApiId: !Ref MyApiWithLambdaRequestAuth Method: any Path: /any/lambda-request + LambdaNotCachedRequest: + Type: Api + Properties: + RestApiId: !Ref MyApiWithNotCachedLambdaRequestAuth + Method: get + Path: /not-cached-lambda-request + MyUserPool: Type: AWS::Cognito::UserPool Properties: diff --git a/tests/translator/input/api_with_identity_intrinsic.yaml b/tests/translator/input/api_with_identity_intrinsic.yaml new file mode 100644 index 0000000000..2afc3d679a --- /dev/null +++ b/tests/translator/input/api_with_identity_intrinsic.yaml @@ -0,0 +1,22 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 + +Conditions: + isProd: true + + +Resources: + APIGateway: + Type: 'AWS::Serverless::Api' + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: SomeAuthorizer + Authorizers: + SomeAuthorizer: + FunctionPayloadType: REQUEST + FunctionArn: SomeArn + Identity: + Headers: + - Accept + ReauthorizeEvery: !If [isProd, 3600, 0] \ No newline at end of file diff --git a/tests/translator/output/api_with_auth_all_minimum.json b/tests/translator/output/api_with_auth_all_minimum.json index 186f6c50d2..a64255ce5a 100644 --- a/tests/translator/output/api_with_auth_all_minimum.json +++ b/tests/translator/output/api_with_auth_all_minimum.json @@ -1,22 +1,221 @@ { "Resources": { + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/any/cognito": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + } + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment6a32cc7f63" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/any/lambda-request": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + }, + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiWithLambdaTokenAuthDeployment03cc3fd4fd": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 03cc3fd4fd00e795fb067f94da06cb2fcfe95d3b", + "StageName": "Stage" + } + }, + "MyApiWithCognitoAuthDeploymentdcc28e4b5f": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: dcc28e4b5f8fbdb114c4da86eae5deddc368c60e", + "StageName": "Stage" + } + }, "MyUserPool": { "Type": "AWS::Cognito::UserPool", "Properties": { + "UsernameAttributes": [ + "email" + ], "UserPoolName": "UserPoolName", "Policies": { "PasswordPolicy": { "MinimumLength": 8 } }, - "UsernameAttributes": [ - "email" - ], "Schema": [ { "AttributeDataType": "String", - "Name": "email", - "Required": false + "Required": false, + "Name": "email" } ] } @@ -24,11 +223,11 @@ "MyAuthFn": { "Type": "AWS::Lambda::Function", "Properties": { + "Handler": "index.handler", "Code": { "S3Bucket": "bucket", "S3Key": "key" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthFnRole", @@ -38,63 +237,31 @@ "Runtime": "nodejs12.x", "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", + "MyFnLambdaRequestAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" } } ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "MyFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "bucket", - "S3Key": "key" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] + } } }, "MyFnRole": { @@ -121,234 +288,61 @@ ], "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyFnCognitoAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", - { - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaTokenAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, "MyFnCognitoPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Ref": "MyFn" }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" + } } ] } } }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeploymentdcc28e4b5f" }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "swagger": "2.0", - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] - } - }, - "/any/cognito": { - "x-amazon-apigateway-any-method": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] - } - } - }, - "securityDefinitions": { - "MyCognitoAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header", - "x-amazon-apigateway-authtype": "cognito_user_pools", - "x-amazon-apigateway-authorizer": { - "type": "cognito_user_pools", - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" - ] - } - ] - } - } - } - } - } - }, - "MyApiWithCognitoAuthDeploymentdcc28e4b5f": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "Description": "RestApi deployment id: dcc28e4b5f8fbdb114c4da86eae5deddc368c60e", "RestApiId": { "Ref": "MyApiWithCognitoAuth" }, - "StageName": "Stage" + "StageName": "Prod" } }, - "MyApiWithCognitoAuthProdStage": { + "MyApiWithNotCachedLambdaRequestAuthProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiWithCognitoAuthDeploymentdcc28e4b5f" + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeployment444f67cd7c" }, "RestApiId": { - "Ref": "MyApiWithCognitoAuth" + "Ref": "MyApiWithNotCachedLambdaRequestAuth" }, "StageName": "Prod" } }, - "MyApiWithLambdaTokenAuth": { + "MyApiWithNotCachedLambdaRequestAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { @@ -356,49 +350,33 @@ } }, "paths": { - "/lambda-token": { + "/not-cached-lambda-request": { "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyLambdaTokenAuth": [] - } - ] - } - }, - "/any/lambda-token": { - "x-amazon-apigateway-any-method": { - "x-amazon-apigateway-integration": { "type": "aws_proxy", - "httpMethod": "POST", "uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaTokenAuth": [] + "MyLambdaRequestAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaTokenAuth": { - "type": "apiKey", - "name": "Authorization", + "MyLambdaRequestAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Unused", "x-amazon-apigateway-authorizer": { - "type": "token", + "type": "request", + "authorizerResultTtlInSeconds": 0, "authorizerUri": { "Fn::Sub": [ "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -412,49 +390,91 @@ } ] } - } + }, + "x-amazon-apigateway-authtype": "custom" } } } } }, - "MyApiWithLambdaTokenAuthDeployment03cc3fd4fd": { - "Type": "AWS::ApiGateway::Deployment", + "MyFnLambdaTokenAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "Description": "RestApi deployment id: 03cc3fd4fd00e795fb067f94da06cb2fcfe95d3b", - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "StageName": "Stage" + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } } }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", + "MyFnCognitoAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment03cc3fd4fd" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "StageName": "Prod" + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } } }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Fn::GetAtt": [ "MyAuthFn", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithLambdaTokenAuth" } @@ -463,11 +483,31 @@ } } }, - "MyApiWithLambdaRequestAuth": { + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { @@ -475,49 +515,49 @@ } }, "paths": { - "/lambda-request": { + "/lambda-token": { "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } }, - "/any/lambda-request": { + "/any/lambda-token": { "x-amazon-apigateway-any-method": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaRequestAuth": { - "type": "apiKey", - "name": "Unused", + "MyLambdaTokenAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Authorization", "x-amazon-apigateway-authorizer": { - "type": "request", + "type": "token", "authorizerUri": { "Fn::Sub": [ "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -530,9 +570,9 @@ } } ] - }, - "identitySource": "method.request.header.Authorization1" - } + } + }, + "x-amazon-apigateway-authtype": "custom" } } } @@ -541,47 +581,131 @@ "MyApiWithLambdaRequestAuthDeployment6a32cc7f63": { "Type": "AWS::ApiGateway::Deployment", "Properties": { - "Description": "RestApi deployment id: 6a32cc7f63485b93190f441a47da57f43de6a532", "RestApiId": { "Ref": "MyApiWithLambdaRequestAuth" }, + "Description": "RestApi deployment id: 6a32cc7f63485b93190f441a47da57f43de6a532", "StageName": "Stage" } }, - "MyApiWithLambdaRequestAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", - "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaRequestAuthDeployment6a32cc7f63" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "StageName": "Prod" - } - }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Fn::GetAtt": [ "MyAuthFn", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", { "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" } } ] } } + }, + "MyAuthFnRole": { + "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" + } + ] + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment03cc3fd4fd" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithNotCachedLambdaRequestAuthDeployment444f67cd7c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 444f67cd7c6475a698a0101480ba99b498325e90", + "StageName": "Stage" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/api_with_identity_intrinsic.json b/tests/translator/output/api_with_identity_intrinsic.json new file mode 100644 index 0000000000..32c8b8eaaf --- /dev/null +++ b/tests/translator/output/api_with_identity_intrinsic.json @@ -0,0 +1,90 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "isProd": true + }, + "Resources": { + "APIGateway": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": {}, + "swagger": "2.0", + "securityDefinitions": { + "SomeAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": { + "Fn::If": [ + "isProd", + 3600, + 0 + ] + }, + "identitySource": "method.request.header.Accept", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "SomeArn" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "APIGatewaySomeAuthorizerAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "SomeArn", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "APIGateway" + } + } + ] + } + } + }, + "APIGatewayDeploymenta119f04c8a": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "APIGateway" + }, + "Description": "RestApi deployment id: a119f04c8aba206b5b7db5f232f013b816fe6447", + "StageName": "Stage" + } + }, + "APIGatewayProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "APIGatewayDeploymenta119f04c8a" + }, + "RestApiId": { + "Ref": "APIGateway" + }, + "StageName": "Prod" + } + } + } +} diff --git a/tests/translator/output/aws-cn/api_with_auth_all_minimum.json b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json index b9e408189c..b90828c4a3 100644 --- a/tests/translator/output/aws-cn/api_with_auth_all_minimum.json +++ b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json @@ -1,22 +1,215 @@ { "Resources": { + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/any/cognito": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithNotCachedLambdaRequestAuthDeployment234e92eab4": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 234e92eab4e4c590ad261ddd55775c1edcc2972f", + "StageName": "Stage" + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/any/lambda-request": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + }, + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, "MyUserPool": { "Type": "AWS::Cognito::UserPool", "Properties": { + "UsernameAttributes": [ + "email" + ], "UserPoolName": "UserPoolName", "Policies": { "PasswordPolicy": { "MinimumLength": 8 } }, - "UsernameAttributes": [ - "email" - ], "Schema": [ { "AttributeDataType": "String", - "Name": "email", - "Required": false + "Required": false, + "Name": "email" } ] } @@ -24,11 +217,11 @@ "MyAuthFn": { "Type": "AWS::Lambda::Function", "Properties": { + "Handler": "index.handler", "Code": { "S3Bucket": "bucket", "S3Key": "key" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthFnRole", @@ -38,63 +231,31 @@ "Runtime": "nodejs12.x", "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", + "MyFnLambdaRequestAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" } } ] - }, - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] - } - }, - "MyFn": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "bucket", - "S3Key": "key" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] + } } }, "MyFnRole": { @@ -121,242 +282,61 @@ ], "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyFnCognitoAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", - { - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaTokenAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, "MyFnCognitoPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Ref": "MyFn" }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "swagger": "2.0", - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] - } - }, - "/any/cognito": { - "x-amazon-apigateway-any-method": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] } } - }, - "securityDefinitions": { - "MyCognitoAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header", - "x-amazon-apigateway-authtype": "cognito_user_pools", - "x-amazon-apigateway-authorizer": { - "type": "cognito_user_pools", - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" - ] - } - ] - } - } - } - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" ] } } }, - "MyApiWithCognitoAuthDeployment5d6fbaaea5": { - "Type": "AWS::ApiGateway::Deployment", + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", "Properties": { - "Description": "RestApi deployment id: 5d6fbaaea5286fd32d64239db8b7f2247cb3f2b5", + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment5d6fbaaea5" + }, "RestApiId": { "Ref": "MyApiWithCognitoAuth" }, - "StageName": "Stage" + "StageName": "Prod" } }, - "MyApiWithCognitoAuthProdStage": { + "MyApiWithNotCachedLambdaRequestAuthProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiWithCognitoAuthDeployment5d6fbaaea5" + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeployment234e92eab4" }, "RestApiId": { - "Ref": "MyApiWithCognitoAuth" + "Ref": "MyApiWithNotCachedLambdaRequestAuth" }, "StageName": "Prod" } }, - "MyApiWithLambdaTokenAuth": { + "MyApiWithNotCachedLambdaRequestAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { @@ -364,49 +344,33 @@ } }, "paths": { - "/lambda-token": { + "/not-cached-lambda-request": { "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyLambdaTokenAuth": [] - } - ] - } - }, - "/any/lambda-token": { - "x-amazon-apigateway-any-method": { - "x-amazon-apigateway-integration": { "type": "aws_proxy", - "httpMethod": "POST", "uri": { "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaTokenAuth": [] + "MyLambdaRequestAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaTokenAuth": { - "type": "apiKey", - "name": "Authorization", + "MyLambdaRequestAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Unused", "x-amazon-apigateway-authorizer": { - "type": "token", + "type": "request", + "authorizerResultTtlInSeconds": 0, "authorizerUri": { "Fn::Sub": [ "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -420,57 +384,109 @@ } ] } - } + }, + "x-amazon-apigateway-authtype": "custom" } } }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, "EndpointConfiguration": { "Types": [ "REGIONAL" ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" } } }, "MyApiWithLambdaTokenAuthDeployment79a03805ba": { "Type": "AWS::ApiGateway::Deployment", "Properties": { - "Description": "RestApi deployment id: 79a03805ba3abc1f005e1282f19bb79af68b4f96", "RestApiId": { "Ref": "MyApiWithLambdaTokenAuth" }, + "Description": "RestApi deployment id: 79a03805ba3abc1f005e1282f19bb79af68b4f96", "StageName": "Stage" } }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", + "MyFnLambdaTokenAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment79a03805ba" + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "StageName": "Prod" + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } } }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Fn::GetAtt": [ "MyAuthFn", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithLambdaTokenAuth" } @@ -479,11 +495,31 @@ } } }, - "MyApiWithLambdaRequestAuth": { + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { @@ -491,49 +527,49 @@ } }, "paths": { - "/lambda-request": { + "/lambda-token": { "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } }, - "/any/lambda-request": { + "/any/lambda-token": { "x-amazon-apigateway-any-method": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaRequestAuth": { - "type": "apiKey", - "name": "Unused", + "MyLambdaTokenAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Authorization", "x-amazon-apigateway-authorizer": { - "type": "request", + "type": "token", "authorizerUri": { "Fn::Sub": [ "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -546,30 +582,20 @@ } } ] - }, - "identitySource": "method.request.header.Authorization1" - } + } + }, + "x-amazon-apigateway-authtype": "custom" } } }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, "EndpointConfiguration": { "Types": [ "REGIONAL" ] - } - } - }, - "MyApiWithLambdaRequestAuthDeployment12aa7114ad": { - "Type": "AWS::ApiGateway::Deployment", - "Properties": { - "Description": "RestApi deployment id: 12aa7114ad8cd8aaeffd832e49f6f8aa8b6c2062", - "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" }, - "StageName": "Stage" + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } } }, "MyApiWithLambdaRequestAuthProdStage": { @@ -584,28 +610,134 @@ "StageName": "Prod" } }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Fn::GetAtt": [ "MyAuthFn", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", { "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" } } ] } } + }, + "MyApiWithLambdaRequestAuthDeployment12aa7114ad": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 12aa7114ad8cd8aaeffd832e49f6f8aa8b6c2062", + "StageName": "Stage" + } + }, + "MyAuthFnRole": { + "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" + } + ] + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment79a03805ba" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithCognitoAuthDeployment5d6fbaaea5": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 5d6fbaaea5286fd32d64239db8b7f2247cb3f2b5", + "StageName": "Stage" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } } } } \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_identity_intrinsic.json b/tests/translator/output/aws-cn/api_with_identity_intrinsic.json new file mode 100644 index 0000000000..84b61b86c7 --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_identity_intrinsic.json @@ -0,0 +1,98 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "isProd": true + }, + "Resources": { + "APIGateway": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": {}, + "swagger": "2.0", + "securityDefinitions": { + "SomeAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": { + "Fn::If": [ + "isProd", + 3600, + 0 + ] + }, + "identitySource": "method.request.header.Accept", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "SomeArn" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "APIGatewaySomeAuthorizerAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "SomeArn", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "APIGateway" + } + } + ] + } + } + }, + "APIGatewayProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "APIGatewayDeployment2621a8c79f" + }, + "RestApiId": { + "Ref": "APIGateway" + }, + "StageName": "Prod" + } + }, + "APIGatewayDeployment2621a8c79f": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "APIGateway" + }, + "Description": "RestApi deployment id: 2621a8c79f8f26195374aad642039f511d020a75", + "StageName": "Stage" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json index 9b583ee6f2..16b4cfc027 100644 --- a/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json +++ b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json @@ -1,22 +1,227 @@ { "Resources": { + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + }, + "/any/cognito": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyCognitoAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [ + { + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + } + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment468dce6129" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/any/lambda-request": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + }, + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [ + { + "MyLambdaRequestAuth": [] + } + ], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithCognitoAuthDeployment492f1347b1": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 492f1347b1194457232f0e99ced4a86954fdeec9", + "StageName": "Stage" + } + }, "MyUserPool": { "Type": "AWS::Cognito::UserPool", "Properties": { + "UsernameAttributes": [ + "email" + ], "UserPoolName": "UserPoolName", "Policies": { "PasswordPolicy": { "MinimumLength": 8 } }, - "UsernameAttributes": [ - "email" - ], "Schema": [ { "AttributeDataType": "String", - "Name": "email", - "Required": false + "Required": false, + "Name": "email" } ] } @@ -24,11 +229,11 @@ "MyAuthFn": { "Type": "AWS::Lambda::Function", "Properties": { + "Handler": "index.handler", "Code": { "S3Bucket": "bucket", "S3Key": "key" }, - "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "MyAuthFnRole", @@ -38,63 +243,41 @@ "Runtime": "nodejs12.x", "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyAuthFnRole": { - "Type": "AWS::IAM::Role", + "MyFnLambdaRequestAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", { - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" } } ] - }, - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] + } } }, - "MyFn": { - "Type": "AWS::Lambda::Function", + "MyApiWithNotCachedLambdaRequestAuthDeploymentd3b8858811": { + "Type": "AWS::ApiGateway::Deployment", "Properties": { - "Code": { - "S3Bucket": "bucket", - "S3Key": "key" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "MyFnRole", - "Arn" - ] + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" }, - "Runtime": "nodejs12.x", - "Tags": [ - { - "Key": "lambda:createdBy", - "Value": "SAM" - } - ] + "Description": "RestApi deployment id: d3b8858811d6c42be45490ba4d1ca059821cf4fd", + "StageName": "Stage" } }, "MyFnRole": { @@ -121,222 +304,40 @@ ], "Tags": [ { - "Key": "lambda:createdBy", - "Value": "SAM" + "Value": "SAM", + "Key": "lambda:createdBy" } ] } }, - "MyFnCognitoAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", - { - "__ApiId__": { - "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaTokenAnyMethodPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, "MyFnCognitoPermissionProd": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Ref": "MyFn" }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithCognitoAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaRequestPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyFnLambdaTokenPermissionProd": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "MyFn" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaTokenAuth" - }, - "__Stage__": "*" - } - ] - } - } - }, - "MyApiWithCognitoAuth": { - "Type": "AWS::ApiGateway::RestApi", - "Properties": { - "Body": { - "swagger": "2.0", - "info": { - "version": "1.0", - "title": { - "Ref": "AWS::StackName" - } - }, - "paths": { - "/cognito": { - "get": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] - } - }, - "/any/cognito": { - "x-amazon-apigateway-any-method": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyCognitoAuth": [] - } - ] - } - } - }, - "securityDefinitions": { - "MyCognitoAuth": { - "type": "apiKey", - "name": "Authorization", - "in": "header", - "x-amazon-apigateway-authtype": "cognito_user_pools", - "x-amazon-apigateway-authorizer": { - "type": "cognito_user_pools", - "providerARNs": [ - { - "Fn::GetAtt": [ - "MyUserPool", - "Arn" - ] - } - ] } } - } - }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, - "EndpointConfiguration": { - "Types": [ - "REGIONAL" ] } } }, - "MyApiWithCognitoAuthDeployment492f1347b1": { + "MyApiWithLambdaTokenAuthDeployment5f3dce4e5c": { "Type": "AWS::ApiGateway::Deployment", "Properties": { - "Description": "RestApi deployment id: 492f1347b1194457232f0e99ced4a86954fdeec9", "RestApiId": { - "Ref": "MyApiWithCognitoAuth" + "Ref": "MyApiWithLambdaTokenAuth" }, + "Description": "RestApi deployment id: 5f3dce4e5c196ff885a155dd8cc0ffeebd5b93b1", "StageName": "Stage" } }, @@ -352,61 +353,56 @@ "StageName": "Prod" } }, - "MyApiWithLambdaTokenAuth": { + "MyApiWithNotCachedLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuthDeploymentd3b8858811" + }, + "RestApiId": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyApiWithNotCachedLambdaRequestAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { "Ref": "AWS::StackName" - } - }, - "paths": { - "/lambda-token": { - "get": { - "x-amazon-apigateway-integration": { - "type": "aws_proxy", - "httpMethod": "POST", - "uri": { - "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" - } - }, - "responses": {}, - "security": [ - { - "MyLambdaTokenAuth": [] - } - ] - } - }, - "/any/lambda-token": { - "x-amazon-apigateway-any-method": { + } + }, + "paths": { + "/not-cached-lambda-request": { + "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaTokenAuth": [] + "MyLambdaRequestAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaTokenAuth": { - "type": "apiKey", - "name": "Authorization", + "MyLambdaRequestAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Unused", "x-amazon-apigateway-authorizer": { - "type": "token", + "type": "request", + "authorizerResultTtlInSeconds": 0, "authorizerUri": { "Fn::Sub": [ "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -420,57 +416,99 @@ } ] } - } + }, + "x-amazon-apigateway-authtype": "custom" } } }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, "EndpointConfiguration": { "Types": [ "REGIONAL" ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" } } }, - "MyApiWithLambdaTokenAuthDeployment5f3dce4e5c": { - "Type": "AWS::ApiGateway::Deployment", + "MyFnLambdaTokenAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "Description": "RestApi deployment id: 5f3dce4e5c196ff885a155dd8cc0ffeebd5b93b1", - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "StageName": "Stage" + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } } }, - "MyApiWithLambdaTokenAuthProdStage": { - "Type": "AWS::ApiGateway::Stage", + "MyFnCognitoAnyMethodPermissionProd": { + "Type": "AWS::Lambda::Permission", "Properties": { - "DeploymentId": { - "Ref": "MyApiWithLambdaTokenAuthDeployment5f3dce4e5c" - }, - "RestApiId": { - "Ref": "MyApiWithLambdaTokenAuth" + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" }, - "StageName": "Prod" + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/*/any/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } } }, - "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { "Type": "AWS::Lambda::Permission", "Properties": { "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", "FunctionName": { "Fn::GetAtt": [ "MyAuthFn", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", "__ApiId__": { "Ref": "MyApiWithLambdaTokenAuth" } @@ -479,11 +517,31 @@ } } }, - "MyApiWithLambdaRequestAuth": { + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { "Type": "AWS::ApiGateway::RestApi", "Properties": { "Body": { - "swagger": "2.0", "info": { "version": "1.0", "title": { @@ -491,49 +549,49 @@ } }, "paths": { - "/lambda-request": { + "/lambda-token": { "get": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } }, - "/any/lambda-request": { + "/any/lambda-token": { "x-amazon-apigateway-any-method": { "x-amazon-apigateway-integration": { - "type": "aws_proxy", "httpMethod": "POST", + "type": "aws_proxy", "uri": { "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" } }, - "responses": {}, "security": [ { - "MyLambdaRequestAuth": [] + "MyLambdaTokenAuth": [] } - ] + ], + "responses": {} } } }, + "swagger": "2.0", "securityDefinitions": { - "MyLambdaRequestAuth": { - "type": "apiKey", - "name": "Unused", + "MyLambdaTokenAuth": { "in": "header", - "x-amazon-apigateway-authtype": "custom", + "type": "apiKey", + "name": "Authorization", "x-amazon-apigateway-authorizer": { - "type": "request", + "type": "token", "authorizerUri": { "Fn::Sub": [ "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", @@ -546,66 +604,140 @@ } } ] - }, - "identitySource": "method.request.header.Authorization1" - } + } + }, + "x-amazon-apigateway-authtype": "custom" } } }, - "Parameters": { - "endpointConfigurationTypes": "REGIONAL" - }, "EndpointConfiguration": { "Types": [ "REGIONAL" ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithNotCachedLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaNotCachedRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/not-cached-lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithNotCachedLambdaRequestAuth" + } + } + ] } } }, "MyApiWithLambdaRequestAuthDeployment468dce6129": { "Type": "AWS::ApiGateway::Deployment", "Properties": { - "Description": "RestApi deployment id: 468dce61296ac92bf536be6fc55751d9553dbc4b", "RestApiId": { "Ref": "MyApiWithLambdaRequestAuth" }, + "Description": "RestApi deployment id: 468dce61296ac92bf536be6fc55751d9553dbc4b", "StageName": "Stage" } }, - "MyApiWithLambdaRequestAuthProdStage": { + "MyAuthFnRole": { + "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" + } + ] + } + }, + "MyApiWithLambdaTokenAuthProdStage": { "Type": "AWS::ApiGateway::Stage", "Properties": { "DeploymentId": { - "Ref": "MyApiWithLambdaRequestAuthDeployment468dce6129" + "Ref": "MyApiWithLambdaTokenAuthDeployment5f3dce4e5c" }, "RestApiId": { - "Ref": "MyApiWithLambdaRequestAuth" + "Ref": "MyApiWithLambdaTokenAuth" }, "StageName": "Prod" } }, - "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { - "Type": "AWS::Lambda::Permission", + "MyFn": { + "Type": "AWS::Lambda::Function", "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { + "Handler": "index.handler", + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Role": { "Fn::GetAtt": [ - "MyAuthFn", + "MyFnRole", "Arn" ] }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", - { - "__ApiId__": { - "Ref": "MyApiWithLambdaRequestAuth" - } - } - ] - } + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/aws-us-gov/api_with_identity_intrinsic.json b/tests/translator/output/aws-us-gov/api_with_identity_intrinsic.json new file mode 100644 index 0000000000..098ebbf10e --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_identity_intrinsic.json @@ -0,0 +1,98 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Conditions": { + "isProd": true + }, + "Resources": { + "APIGateway": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": {}, + "swagger": "2.0", + "securityDefinitions": { + "SomeAuthorizer": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": { + "Fn::If": [ + "isProd", + 3600, + 0 + ] + }, + "identitySource": "method.request.header.Accept", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "SomeArn" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "APIGatewayDeploymentbbcece046c": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "APIGateway" + }, + "Description": "RestApi deployment id: bbcece046c6ecd35f10c6ba88cf762d87ef35e8a", + "StageName": "Stage" + } + }, + "APIGatewaySomeAuthorizerAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "SomeArn", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "APIGateway" + } + } + ] + } + } + }, + "APIGatewayProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "APIGatewayDeploymentbbcece046c" + }, + "RestApiId": { + "Ref": "APIGateway" + }, + "StageName": "Prod" + } + } + } +} diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 03339c30e2..6c702c4e41 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -316,6 +316,7 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "api_with_gateway_responses_minimal", "api_with_gateway_responses_implicit", "api_with_gateway_responses_string_status_code", + "api_with_identity_intrinsic", "api_cache", "api_with_access_log_setting", "api_with_any_method_in_swagger", From b26769dd49645e381c4909250e7851811cbd134a Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Tue, 27 Jul 2021 13:50:46 -0500 Subject: [PATCH 25/28] chore: Update PR Checklist to include writing intrinsic tests (#2106) * Update PR Checklist to include writing intrinsic tests * Handle pr feedback Co-authored-by: Jacob Fuss --- .github/PULL_REQUEST_TEMPLATE.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31562ac68b..1b91b346ef 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,10 @@ *Checklist:* -- [ ] Write/update tests +- [ ] Add/update tests using: + - [ ] Correct values + - [ ] Bad/wrong values (None, empty, wrong type, length, etc.) + - [ ] [Intrinsic Functions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html) - [ ] `make pr` passes - [ ] Update documentation - [ ] Verify transformed template deploys and application functions as expected From 23ecdf48ec5bb8e7dd41efff16a457c6ed116d5f Mon Sep 17 00:00:00 2001 From: Ichinose Shogo Date: Wed, 28 Jul 2021 05:48:19 +0900 Subject: [PATCH 26/28] Delete .travis.yml (#2102) --- .travis.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4e4b1c0085..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -# Enable container based builds -sudo: false -dist: xenial # required for Python >= 3.7 -language: python - -matrix: - include: - - python: 3.8 - env: - - TOXENV=py38 - - python: 3.7 - env: - - TOXENV=py37 - - python: 3.6 - env: - - TOXENV=py36 - - python: 2.7 - env: - - TOXENV=py27 - - -install: -# Install the code requirements -- make init - -# Install Docs requirements - -script: -# Runs unit tests -- tox From 274cb7d4849b8a82a6dc98a9026aa72d2ed2b042 Mon Sep 17 00:00:00 2001 From: Ryan Parman Date: Wed, 28 Jul 2021 03:08:20 +0300 Subject: [PATCH 27/28] fix: Correct grammar in the waiting for changeset message (#2027) --- integration/helpers/deployer/deployer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/helpers/deployer/deployer.py b/integration/helpers/deployer/deployer.py index a6d6c7d884..8a4422a29e 100644 --- a/integration/helpers/deployer/deployer.py +++ b/integration/helpers/deployer/deployer.py @@ -284,7 +284,7 @@ def wait_for_changeset(self, changeset_id, stack_name): :param stack_name: Stack name :return: Latest status of the create-change-set operation """ - sys.stdout.write("\nWaiting for changeset to be created..\n") + sys.stdout.write("\nWaiting for changeset to be created...\n") sys.stdout.flush() # Wait for changeset to be created From 7a990527bb4d656c2fb9e3276c3e624868c01d31 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:46:16 -0500 Subject: [PATCH 28/28] chore: bump version to 1.39.0 (#2128) --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 4ee5c8be11..773c90f956 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.38.0" +__version__ = "1.39.0"