diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31562ac68..1b91b346e 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 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4e4b1c008..000000000 --- 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 diff --git a/Makefile b/Makefile index 09dce252d..9675b8ccb 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/README.md b/README.md index 09e8c5638..c2d6f6b70 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), diff --git a/docs/globals.rst b/docs/globals.rst index 5d1469e43..22cfc933d 100644 --- a/docs/globals.rst +++ b/docs/globals.rst @@ -70,6 +70,7 @@ Currently, the following resources and properties are being supported: PermissionsBoundary: ReservedConcurrentExecutions: EventInvokeConfig: + Architectures: Api: # Properties of AWS::Serverless::Api diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst index 8b2760d61..ae35e2249 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 diff --git a/integration/combination/__init__.py b/integration/combination/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/integration/combination/test_api_settings.py b/integration/combination/test_api_settings.py new file mode 100644 index 000000000..5752ba9ae --- /dev/null +++ b/integration/combination/test_api_settings.py @@ -0,0 +1,181 @@ +import hashlib + +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +import requests +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + + +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_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_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)) + + +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 000000000..d08a27a47 --- /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 000000000..abc3a5d06 --- /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 000000000..1ff2efc76 --- /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 000000000..5021de9cd --- /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 000000000..d77b16a68 --- /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 000000000..e605c5ff3 --- /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 000000000..8aab5a472 --- /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 000000000..a8647003c --- /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 000000000..523aa5e46 --- /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 000000000..e04219898 --- /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 000000000..e00974ba9 --- /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 000000000..17ce96710 --- /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 000000000..92491fd84 --- /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 000000000..b2b2461ee --- /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 000000000..eb702679b --- /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 000000000..48d357b5b --- /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 000000000..139f3254a --- /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 000000000..4c77fd201 --- /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 000000000..fdaa8ebb9 --- /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 000000000..2e5af72aa --- /dev/null +++ b/integration/combination/test_function_with_kinesis.py @@ -0,0 +1,47 @@ +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"]) + + +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/combination/test_function_with_layers.py b/integration/combination/test_function_with_layers.py new file mode 100644 index 000000000..c75a509da --- /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 000000000..a87bddd75 --- /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 000000000..cb855f1db --- /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 000000000..90ddb3016 --- /dev/null +++ b/integration/combination/test_function_with_s3_bucket.py @@ -0,0 +1,32 @@ +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(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/combination/test_function_with_schedule.py b/integration/combination/test_function_with_schedule.py new file mode 100644 index 000000000..746355a38 --- /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 000000000..663dfc0cb --- /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 000000000..7d79937bc --- /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 000000000..f83f90836 --- /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 000000000..9f7dd597e --- /dev/null +++ b/integration/combination/test_function_with_sns.py @@ -0,0 +1,48 @@ +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.assertIn(lambda_function_endpoint, 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) + + 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/combination/test_function_with_sqs.py b/integration/combination/test_function_with_sqs.py new file mode 100644 index 000000000..e5f54cc2d --- /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 000000000..ba9ca9f26 --- /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 000000000..963e44738 --- /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 000000000..a51db8303 --- /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 000000000..3012e1b85 --- /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 000000000..05d9cd5ce --- /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 000000000..e7221ea41 --- /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 000000000..a09f06f68 --- /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 000000000..c7c39a8c9 --- /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 000000000..7b957416a --- /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 000000000..4bea0d299 --- /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 000000000..f4112601d --- /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 000000000..8516efc0d --- /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 000000000..3166ae7af --- /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 000000000..64918f9ec --- /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/config/region_service_exclusion.yaml b/integration/config/region_service_exclusion.yaml index a7e63ee42..d2241a114 100644 --- a/integration/config/region_service_exclusion.yaml +++ b/integration/config/region_service_exclusion.yaml @@ -8,13 +8,16 @@ regions: - IoT - GatewayResponses - HttpApi + - ARM ap-east-1: - Cognito - IoT - ServerlessRepo - HttpApi + - ARM ap-northeast-2: - HttpApi + - ARM ap-northeast-3: - Cognito - IoT @@ -22,6 +25,7 @@ regions: - XRay - CodeDeploy - HttpApi + - ARM ap-south-1: - HttpApi ap-southeast-1: @@ -30,6 +34,7 @@ regions: - Cognito - IoT - HttpApi + - ARM cn-north-1: - ServerlessRepo - Cognito @@ -39,6 +44,7 @@ regions: - IoT - GatewayResponses - HttpApi + - ARM cn-northwest-1: - ServerlessRepo - Cognito @@ -48,12 +54,14 @@ regions: - IoT - GatewayResponses - HttpApi + - ARM eu-north-1: - ServerlessRepo - Cognito - IoT - HttpApi - Layers + - ARM eu-south-1: - ServerlessRepo - Cognito @@ -63,6 +71,7 @@ regions: - IoT - GatewayResponses - HttpApi + - ARM eu-west-2: - HttpApi eu-west-3: @@ -70,17 +79,21 @@ regions: - IoT - XRay - HttpApi + - ARM me-south-1: - ServerlessRepo - Cognito - IoT - HttpApi + - ARM sa-east-1: - IoT - Cognito - HttpApi + - ARM us-east-2: - HttpApi us-west-1: - Cognito - IoT + - ARM diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py index e12410695..06a10a56b 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 2ffab0e19..5686f4755 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 000000000..c261fed5d --- /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 4cb0de31f..8a4422a29 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__) @@ -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 diff --git a/integration/helpers/deployer/utils/retry.py b/integration/helpers/deployer/utils/retry.py new file mode 100644 index 000000000..ab6b07258 --- /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 000000000..d2a87e9a7 --- /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 eadd53336..5f8aca2ce 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 0f4632303..6a31d1a77 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): @@ -151,3 +159,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/metrics/__init__.py b/integration/metrics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/integration/metrics/test_metrics_integration.py b/integration/metrics/test_metrics_integration.py new file mode 100644 index 000000000..e2a69b757 --- /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/integration/resources/code/AWS_logo_RGB.png b/integration/resources/code/AWS_logo_RGB.png new file mode 100644 index 000000000..3edf631f2 Binary files /dev/null and b/integration/resources/code/AWS_logo_RGB.png differ diff --git a/integration/resources/code/MTLSCert-Updated.pem b/integration/resources/code/MTLSCert-Updated.pem new file mode 100644 index 000000000..8d9d11b05 --- /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 000000000..8d9d11b05 --- /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 000000000..3328a1bb5 Binary files /dev/null and b/integration/resources/code/binary-media.zip differ diff --git a/integration/resources/code/code2.zip b/integration/resources/code/code2.zip new file mode 100644 index 000000000..2d496009d Binary files /dev/null and b/integration/resources/code/code2.zip differ 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 000000000..c6006004b --- /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 000000000..48e1752b6 --- /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 000000000..44effbbf1 --- /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 000000000..7b3dee7d3 --- /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 000000000..85cbb5b71 --- /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 000000000..bd48f03b9 --- /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 000000000..bd48f03b9 --- /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 000000000..55ec077b3 --- /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 000000000..79a2fbc3a --- /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 000000000..79a2fbc3a --- /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 000000000..79a2fbc3a --- /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 000000000..79a2fbc3a --- /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 000000000..79a2fbc3a --- /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 000000000..79a2fbc3a --- /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 000000000..bd48f03b9 --- /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 000000000..bd48f03b9 --- /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 000000000..03b8a01d5 --- /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 000000000..bd48f03b9 --- /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 000000000..6bb28b0df --- /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 000000000..6bb28b0df --- /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 000000000..7787fc1a1 --- /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 000000000..a92cd0eb4 --- /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 000000000..e99ee073a --- /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 000000000..4ff8679c2 --- /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 000000000..a04e57274 --- /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 000000000..7c4a4a034 --- /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 000000000..c87908d15 --- /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 000000000..178938026 --- /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 000000000..a8de059ba --- /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 000000000..c87908d15 --- /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 000000000..fc771413b --- /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 000000000..7fd7c6312 --- /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 000000000..8fb2e128c --- /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 000000000..c90dda99e --- /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 000000000..96648fa98 --- /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 000000000..4aa5ea974 --- /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 000000000..35a00939c --- /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 000000000..bc5511772 --- /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 000000000..d5a161172 --- /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 000000000..4aa5ea974 --- /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 000000000..2b061ccf4 --- /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 000000000..77ca0a93c --- /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 000000000..4aa5ea974 --- /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 000000000..b76de6e5f --- /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 000000000..5f6e3fe3d --- /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 000000000..7f6ef2926 --- /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 000000000..41048af23 --- /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 000000000..60b6bd921 --- /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 000000000..64511cfc4 --- /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_kinesis_intrinsics.json b/integration/resources/expected/combination/function_with_kinesis_intrinsics.json new file mode 100644 index 000000000..64511cfc4 --- /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/expected/combination/function_with_layer.json b/integration/resources/expected/combination/function_with_layer.json new file mode 100644 index 000000000..05716338a --- /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 000000000..32f9f0422 --- /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 000000000..128c0787c --- /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 000000000..0b96aed90 --- /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 000000000..a257ccb24 --- /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 000000000..1bd08615e --- /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 000000000..d7c8494cb --- /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 000000000..7f0714302 --- /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_s3_intrinsics.json b/integration/resources/expected/combination/function_with_s3_intrinsics.json new file mode 100644 index 000000000..2fdb0626a --- /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/expected/combination/function_with_schedule.json b/integration/resources/expected/combination/function_with_schedule.json new file mode 100644 index 000000000..86e067c17 --- /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 000000000..e0d7734e0 --- /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 000000000..bd0044265 --- /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 000000000..028381361 --- /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 000000000..d7d511135 --- /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_sns_intrinsics.json b/integration/resources/expected/combination/function_with_sns_intrinsics.json new file mode 100644 index 000000000..8d715ef5d --- /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/expected/combination/function_with_sqs.json b/integration/resources/expected/combination/function_with_sqs.json new file mode 100644 index 000000000..188e4c467 --- /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 000000000..f55909f48 --- /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 000000000..102d81d4f --- /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 000000000..8bbb430cd --- /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 000000000..7c7db8ba4 --- /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 000000000..a56523556 --- /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 000000000..a56523556 --- /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 000000000..0893a580e --- /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 000000000..56d4435fc --- /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 000000000..982a54335 --- /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 000000000..22203a11f --- /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 000000000..05f14d153 --- /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 000000000..ee21302f5 --- /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 000000000..a8b630642 --- /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 000000000..692dad06c --- /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 000000000..9571f1a59 --- /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 000000000..38e01119b --- /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 000000000..94b05653d --- /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 000000000..86883f534 --- /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/expected/single/basic_function_with_arm_architecture.json b/integration/resources/expected/single/basic_function_with_arm_architecture.json new file mode 100644 index 000000000..3e8d999df --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_arm_architecture.json @@ -0,0 +1,10 @@ +[ + { + "LogicalResourceId": "MyLambdaFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyLambdaFunctionRole", + "ResourceType": "AWS::IAM::Role" + } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_with_x86_architecture.json b/integration/resources/expected/single/basic_function_with_x86_architecture.json new file mode 100644 index 000000000..3e8d999df --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_x86_architecture.json @@ -0,0 +1,10 @@ +[ + { + "LogicalResourceId": "MyLambdaFunction", + "ResourceType": "AWS::Lambda::Function" + }, + { + "LogicalResourceId": "MyLambdaFunctionRole", + "ResourceType": "AWS::IAM::Role" + } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_layer_with_compatible_architecture.json b/integration/resources/expected/single/basic_layer_with_compatible_architecture.json new file mode 100644 index 000000000..beba3153c --- /dev/null +++ b/integration/resources/expected/single/basic_layer_with_compatible_architecture.json @@ -0,0 +1,3 @@ +[ + { "LogicalResourceId":"MyLayerVersion", "ResourceType":"AWS::Lambda::LayerVersion"} +] \ 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 000000000..0b3fa6c55 --- /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 000000000..b7e9e55fc --- /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 000000000..2eecfc338 --- /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 000000000..4cc07ee30 --- /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 000000000..558a9df2e --- /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 000000000..604196133 --- /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 000000000..60597624b --- /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 000000000..c4a361d7f --- /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 000000000..19b853550 --- /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 000000000..a84c94b7d --- /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 000000000..0b5ccc97a --- /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 000000000..38f54a33d --- /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 000000000..19b853550 --- /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 000000000..d44733539 --- /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 000000000..a4e5cb0ac --- /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 000000000..4866fa80e --- /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 000000000..7b52013ab --- /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 000000000..53e0e8b11 --- /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 000000000..19f08e109 --- /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 000000000..d3449ba8e --- /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 000000000..025e2965f --- /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 000000000..f7d58c3bb --- /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 000000000..d08014b5a --- /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 000000000..6d539f005 --- /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 000000000..6a2f1ca07 --- /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 000000000..2aeb01c18 --- /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 000000000..876ae03d2 --- /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 000000000..196d0196b --- /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 000000000..ff282423b --- /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 000000000..2cd0e4f95 --- /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 000000000..5f96c3fb5 --- /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 000000000..31594ddd5 --- /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 000000000..0c8966b67 --- /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 000000000..99573baa0 --- /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 000000000..66ce01d8a --- /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 000000000..73b3dfcfc --- /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 000000000..d97d9ad15 --- /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 000000000..40a6c546f --- /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 000000000..7bd7c6d6e --- /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 000000000..1d40b0087 --- /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 000000000..1a627a6a3 --- /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 000000000..977f94f69 --- /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 000000000..99b280a02 --- /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 000000000..10dd31bd8 --- /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 000000000..c1d257862 --- /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 000000000..f275e2969 --- /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 000000000..2d0a33a01 --- /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 000000000..4b585a3c8 --- /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 000000000..97d44003c --- /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_kinesis_intrinsics.yaml b/integration/resources/templates/combination/function_with_kinesis_intrinsics.yaml new file mode 100644 index 000000000..8dbd0b8b1 --- /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/integration/resources/templates/combination/function_with_layer.yaml b/integration/resources/templates/combination/function_with_layer.yaml new file mode 100644 index 000000000..f58a29017 --- /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 000000000..703b6c696 --- /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 000000000..962bee9d9 --- /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 000000000..acaf3a383 --- /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 000000000..0fa07ba4a --- /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 000000000..bd0cec1c0 --- /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 000000000..1d091fe91 --- /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 000000000..fa60ce17b --- /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_s3_intrinsics.yaml b/integration/resources/templates/combination/function_with_s3_intrinsics.yaml new file mode 100644 index 000000000..8de86f083 --- /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/integration/resources/templates/combination/function_with_schedule.yaml b/integration/resources/templates/combination/function_with_schedule.yaml new file mode 100644 index 000000000..97cda940b --- /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 000000000..28a5ad2b3 --- /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 000000000..f5eb5581a --- /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 000000000..b05c961b1 --- /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 000000000..7f6f15c3e --- /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_sns_intrinsics.yaml b/integration/resources/templates/combination/function_with_sns_intrinsics.yaml new file mode 100644 index 000000000..08c9f5872 --- /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/integration/resources/templates/combination/function_with_sqs.yaml b/integration/resources/templates/combination/function_with_sqs.yaml new file mode 100644 index 000000000..6cf183459 --- /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 000000000..d1d1a7524 --- /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 000000000..265866fc4 --- /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 000000000..955245dab --- /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 000000000..eecc57f10 --- /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 000000000..443251fc6 --- /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 000000000..2803533d8 --- /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 000000000..85d8bad2c --- /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 000000000..118e107d1 --- /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 000000000..18dfc0e8b --- /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 000000000..0193395f5 --- /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 000000000..2ddb10d4f --- /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 000000000..23946e3d2 --- /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 000000000..c2b8a5dcb --- /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 000000000..5a2185226 --- /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 000000000..463716437 --- /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 000000000..5d1ed150d --- /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 000000000..7f8868902 --- /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 000000000..5abae8a4a --- /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 000000000..ec84d4387 --- /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/resources/templates/single/basic_function_with_arm_architecture.yaml b/integration/resources/templates/single/basic_function_with_arm_architecture.yaml new file mode 100644 index 000000000..c682c5e34 --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_arm_architecture.yaml @@ -0,0 +1,10 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Architectures: ["arm64"] + \ No newline at end of file diff --git a/integration/resources/templates/single/basic_function_with_x86_architecture.yaml b/integration/resources/templates/single/basic_function_with_x86_architecture.yaml new file mode 100644 index 000000000..dbc05dae2 --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_x86_architecture.yaml @@ -0,0 +1,11 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Architectures: + - x86_64 + diff --git a/integration/resources/templates/single/basic_layer_with_compatible_architecture.yaml b/integration/resources/templates/single/basic_layer_with_compatible_architecture.yaml new file mode 100644 index 000000000..084e6ef71 --- /dev/null +++ b/integration/resources/templates/single/basic_layer_with_compatible_architecture.yaml @@ -0,0 +1,28 @@ +Parameters: + Runtimes: + Type: CommaDelimitedList + Default: nodejs12.x,nodejs10.x + LayerName: + Type: String + Default: MyNamedLayerVersion + + +Resources: + MyLayerVersion: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ${contenturi} + LayerName: + Ref: LayerName + CompatibleRuntimes: + Ref: Runtimes + CompatibleArchitectures: [x86_64, arm64] + +Outputs: + MyLayerArn: + Value: + Ref: MyLayerVersion + LayerName: + Value: + Ref: LayerName + diff --git a/integration/single/test_basic_api.py b/integration/single/test_basic_api.py index a54fb1fd3..846d96718 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 43dc9fdfa..15ae0861f 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 888ec6667..1b441a631 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): @@ -44,13 +44,35 @@ def test_function_with_http_api_events(self, file_name): self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT) + @parameterized.expand( + [ + ("single/basic_function", ["x86_64"]), + ("single/basic_function_no_envvar", ["x86_64"]), + ("single/basic_function_openapi", ["x86_64"]), + ("single/basic_function_with_arm_architecture", ["arm64"]), + ("single/basic_function_with_x86_architecture", ["x86_64"]), + ] + ) + @skipIf(current_region_does_not_support(["ARM"]), "ARM is not supported in this testing region") + def test_basic_function_with_architecture(self, file_name, architecture): + """ + Creates a basic lambda function + """ + self.create_and_verify_stack(file_name) + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + function_architecture = lambda_client.get_function_configuration(FunctionName=function_name)["Architectures"] + + self.assertEqual(function_architecture, architecture) + 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 +105,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 +119,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 +136,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 +180,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 e7f3c187d..e7cc3d956 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 e5f2421f4..fbddf5de4 100644 --- a/integration/single/test_basic_layer_version.py +++ b/integration/single/test_basic_layer_version.py @@ -10,11 +10,11 @@ class TestBasicLayerVersion(BaseTest): """ @skipIf(current_region_does_not_support(["Layers"]), "Layers is not supported in this testing region") - def test_basic_layer_version(self): + def test_basic_layer_version(self, filename): """ 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"] @@ -46,3 +46,21 @@ def test_basic_layer_with_parameters(self): self.assertEqual(layer_version_result["LicenseInfo"], license) self.assertEqual(layer_version_result["Description"], description) + + @skipIf(current_region_does_not_support(["Layers"]), "Layers is not supported in this testing region") + @skipIf(current_region_does_not_support(["ARM"]), "ARM is not supported in this testing region") + def test_basic_layer_with_architecture(self): + """ + Creates a basic lambda layer version specifying compatible architecture + """ + self.create_and_verify_stack("single/basic_layer_with_compatible_architecture") + + outputs = self.get_stack_outputs() + layer_arn = outputs["MyLayerArn"] + layer_name = outputs["LayerName"] + + layer_version_result = self.client_provider.lambda_client.get_layer_version_by_arn(Arn=layer_arn) + self.client_provider.lambda_client.delete_layer_version( + LayerName=layer_name, VersionNumber=layer_version_result["Version"] + ) + self.assertEqual(layer_version_result["CompatibleArchitectures"], ["x86_64", "arm64"]) diff --git a/integration/single/test_basic_state_machine.py b/integration/single/test_basic_state_machine.py index d5ff56822..2cadd5acb 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 be3851301..1424f80d6 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' @@ -15,6 +15,7 @@ parameterized~=0.7.4 pathlib2>=2.3.5; python_version < '3' click~=7.1 dateparser~=0.7 +boto3~=1.17 # Requirements for examples requests~=2.24.0 diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 4ee5c8be1..773c90f95 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.38.0" +__version__ = "1.39.0" diff --git a/samtranslator/feature_toggle/feature_toggle.py b/samtranslator/feature_toggle/feature_toggle.py index 2eddcfa1e..cbfcf2a3c 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, diff --git a/samtranslator/intrinsics/resolver.py b/samtranslator/intrinsics/resolver.py index 519f856fe..af79704a9 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/metrics/__init__.py b/samtranslator/metrics/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/samtranslator/metrics/metrics.py b/samtranslator/metrics/metrics.py new file mode 100644 index 000000000..42e83808c --- /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/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 91ef47305..d85404b7b 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( diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 7c91f894e..428ac1972 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, @@ -268,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() @@ -312,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/samtranslator/model/architecture.py b/samtranslator/model/architecture.py new file mode 100644 index 000000000..a6dea46fe --- /dev/null +++ b/samtranslator/model/architecture.py @@ -0,0 +1,5 @@ +""" +Enum for determining type of architectures for Lambda Function. +""" +ARM64 = "arm64" +X86_64 = "x86_64" diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index de0f99637..539689425 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())), } @@ -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} @@ -918,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/samtranslator/model/intrinsics.py b/samtranslator/model/intrinsics.py index c81e40d00..e95a5d6d0 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/lambda_.py b/samtranslator/model/lambda_.py index c473bbd6f..310c5c55e 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -26,6 +26,7 @@ class LambdaFunction(Resource): "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), "CodeSigningConfigArn": PropertyType(False, is_str()), "ImageConfig": PropertyType(False, is_type(dict)), + "Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")} @@ -113,6 +114,7 @@ class LambdaLayerVersion(Resource): "Content": PropertyType(True, is_type(dict)), "Description": PropertyType(False, is_str()), "LayerName": PropertyType(False, is_str()), + "CompatibleArchitectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "CompatibleRuntimes": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "LicenseInfo": PropertyType(False, is_str()), } diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index e8ddd291e..29c0a587d 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -21,6 +21,7 @@ ApiGatewayApiKey, ) from samtranslator.model.apigatewayv2 import ApiGatewayV2Stage, ApiGatewayV2DomainName +from samtranslator.model.architecture import ARM64, X86_64 from samtranslator.model.cloudformation import NestedStack from samtranslator.model.dynamodb import DynamoDBTable from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException @@ -37,6 +38,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, @@ -89,6 +91,7 @@ class SamFunction(SamResourceMacro): "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), "ImageConfig": PropertyType(False, is_type(dict)), "CodeSigningConfigArn": PropertyType(False, is_str()), + "Architectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), } event_resolver = ResourceTypeResolver( samtranslator.model.eventsources, @@ -416,6 +419,7 @@ def _construct_lambda_function(self): lambda_function.FileSystemConfigs = self.FileSystemConfigs lambda_function.ImageConfig = self.ImageConfig lambda_function.PackageType = self.PackageType + lambda_function.Architectures = self.Architectures if self.Tracing: lambda_function.TracingConfig = {"Mode": self.Tracing} @@ -426,6 +430,7 @@ def _construct_lambda_function(self): lambda_function.CodeSigningConfigArn = self.CodeSigningConfigArn self._validate_package_type(lambda_function) + self._validate_architectures(lambda_function) return lambda_function def _add_event_invoke_managed_policy(self, dest_config, logical_id, condition, dest_arn): @@ -540,6 +545,36 @@ def _validate_package_type_image(): # Call appropriate validation function based on the package type. return _validate_per_package_type[packagetype]() + def _validate_architectures(self, lambda_function): + """ + Validates Function based on the existence of architecture type + + parameters + ---------- + lambda_function: LambdaFunction + Object of function properties supported on AWS Lambda + + Raises + ------ + InvalidResourceException + Raised when the Architectures property is invalid + """ + + architectures = [X86_64] if lambda_function.Architectures is None else lambda_function.Architectures + + if is_intrinsic(architectures): + return + + if ( + not isinstance(architectures, list) + or len(architectures) != 1 + or (not is_intrinsic(architectures[0]) and (architectures[0] not in [X86_64, ARM64])) + ): + raise InvalidResourceException( + lambda_function.logical_id, + "Architectures needs to be a list with one string, either `{}` or `{}`.".format(X86_64, ARM64), + ) + def _validate_dlq(self): """Validates whether the DeadLetterQueue LogicalId is validation :raise: InvalidResourceException @@ -1131,6 +1166,7 @@ class SamLayerVersion(SamResourceMacro): "LayerName": PropertyType(False, one_of(is_str(), is_type(dict))), "Description": PropertyType(False, is_str()), "ContentUri": PropertyType(True, one_of(is_str(), is_type(dict))), + "CompatibleArchitectures": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "CompatibleRuntimes": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "LicenseInfo": PropertyType(False, is_str()), "RetentionPolicy": PropertyType(False, is_str()), @@ -1212,6 +1248,9 @@ def _construct_lambda_layer(self, intrinsics_resolver): lambda_layer.LayerName = self.LayerName lambda_layer.Description = self.Description lambda_layer.Content = construct_s3_location_object(self.ContentUri, self.logical_id, "ContentUri") + + lambda_layer.CompatibleArchitectures = self.CompatibleArchitectures + self._validate_architectures(lambda_layer) lambda_layer.CompatibleRuntimes = self.CompatibleRuntimes lambda_layer.LicenseInfo = self.LicenseInfo @@ -1224,6 +1263,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,9 +1281,34 @@ 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]), ) + def _validate_architectures(self, lambda_layer): + """Validate the values inside the CompatibleArchitectures field of a layer + + Parameters + ---------- + lambda_layer: SamLayerVersion + The AWS Lambda layer version to validate + + Raises + ------ + InvalidResourceException + If any of the architectures is not valid + """ + architectures = lambda_layer.CompatibleArchitectures or [X86_64] + # Intrinsics are not validated + if is_intrinsic(architectures): + return + for arq in architectures: + # We validate the values only if we they're not intrinsics + if not is_intrinsic(arq) and not arq in [ARM64, X86_64]: + raise InvalidResourceException( + lambda_layer.logical_id, + "CompatibleArchitectures needs to be a list of '{}' or '{}'".format(X86_64, ARM64), + ) + class SamStateMachine(SamResourceMacro): """SAM state machine macro.""" diff --git a/samtranslator/model/stepfunctions/generators.py b/samtranslator/model/stepfunctions/generators.py index 8e1797c7d..f8431c189 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 fc0f77a26..54e00ed05 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 @@ -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 2f4d9b0e4..fa3ba3601 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__) @@ -41,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. @@ -51,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 @@ -104,6 +107,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") @@ -353,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"] diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index f75beb3ec..950b5583c 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -42,6 +42,7 @@ class Globals(object): "EventInvokeConfig", "FileSystemConfigs", "CodeSigningConfigArn", + "Architectures", ], # Everything except # DefinitionBody: because its hard to reason about merge of Swagger dictionaries diff --git a/samtranslator/region_configuration.py b/samtranslator/region_configuration.py index 712d4faf9..c7c6b55e8 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", + ] diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 85b131855..c5c80ee38 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"] @@ -531,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): @@ -620,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): @@ -698,7 +699,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 +709,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]): @@ -779,6 +782,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. @@ -877,6 +920,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") @@ -898,31 +943,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 @@ -1134,7 +1186,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" @@ -1252,3 +1305,37 @@ 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 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): + """ + 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/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 9394a6c0a..897661e8d 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 99e18e80f..22ea67006 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,8 +44,10 @@ 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()) - 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 +232,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 +242,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/setup.py b/setup.py index 38af9df29..8ca3f5605 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")}, diff --git a/tests/intrinsics/test_resolver.py b/tests/intrinsics/test_resolver.py index 2f62b510f..946307b87 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/metrics/test_metrics.py b/tests/metrics/test_metrics.py new file mode 100644 index 000000000..52165f4cf --- /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) diff --git a/tests/model/test_api.py b/tests/model/test_api.py index 627bda3d5..708b83a5a 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/model/test_sam_resources.py b/tests/model/test_sam_resources.py index f37d82b05..af70901c3 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -5,12 +5,79 @@ from samtranslator.intrinsics.resolver import IntrinsicsResolver from samtranslator.model import InvalidResourceException from samtranslator.model.apigatewayv2 import ApiGatewayV2HttpApi -from samtranslator.model.lambda_ import LambdaFunction, LambdaVersion +from samtranslator.model.lambda_ import LambdaFunction, LambdaLayerVersion, LambdaVersion from samtranslator.model.apigateway import ApiGatewayDeployment, ApiGatewayRestApi from samtranslator.model.apigateway import ApiGatewayStage from samtranslator.model.iam import IAMRole from samtranslator.model.packagetype import IMAGE, ZIP -from samtranslator.model.sam_resources import SamFunction, SamApi, SamHttpApi +from samtranslator.model.sam_resources import SamFunction, SamLayerVersion, SamApi, SamHttpApi + + +class TestArchitecture(TestCase): + kwargs = { + "intrinsics_resolver": IntrinsicsResolver({}), + "event_resources": [], + "managed_policy_map": {"foo": "bar"}, + } + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_unknown_architectures(self): + function = SamFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" + invalid_architectures = [["arm"], [1], "arm", 1, {"my": "value"}, True, [], {}] + for architecture in invalid_architectures: + function.Architectures = architecture + with pytest.raises(InvalidResourceException) as e: + function.to_cloudformation(**self.kwargs) + self.assertEqual( + str(e.value.message), + "Resource with id [foo] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`.", + ) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_multiple_architectures(self): + function = SamFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" + function.Architectures = ["arm64", "x86_64"] + + with pytest.raises(InvalidResourceException) as e: + function.to_cloudformation(**self.kwargs) + self.assertEqual( + str(e.value.message), + "Resource with id [foo] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`.", + ) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_validate_architecture_with_intrinsic(self): + function = SamFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" + function.Architectures = {"Ref": "MyRef"} + + cfnResources = function.to_cloudformation(**self.kwargs) + generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] + self.assertEqual(generatedFunctionList.__len__(), 1) + self.assertEqual(generatedFunctionList[0].Architectures, {"Ref": "MyRef"}) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_valid_architectures(self): + function = SamFunction("foo") + function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" + valid_architectures = (["arm64"], ["x86_64"]) + + for architecture in valid_architectures: + function.Architectures = architecture + cfnResources = function.to_cloudformation(**self.kwargs) + generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] + self.assertEqual(generatedFunctionList.__len__(), 1) + self.assertEqual(generatedFunctionList[0].Architectures, architecture) class TestCodeUriandImageUri(TestCase): @@ -340,3 +407,29 @@ def test_with_passthrough_resource_attributes(self): function = SamFunction("foo", attributes=expected) attributes = function.get_passthrough_resource_attributes() self.assertEqual(attributes, expected) + + +class TestLayers(TestCase): + kwargs = { + "intrinsics_resolver": IntrinsicsResolver({}), + "event_resources": [], + "managed_policy_map": {"foo": "bar"}, + } + + def test_basic_layer(self): + layer = SamLayerVersion("foo") + layer.ContentUri = "s3://foobar/foo.zip" + cfnResources = layer.to_cloudformation(**self.kwargs) + generatedLayerList = [x for x in cfnResources if isinstance(x, LambdaLayerVersion)] + self.assertEqual(cfnResources.__len__(), 1) + self.assertTrue(isinstance(cfnResources[0], LambdaLayerVersion)) + self.assertEqual(cfnResources[0].Content, {"S3Key": "foo.zip", "S3Bucket": "foobar"}) + + def test_invalid_compatible_architectures(self): + layer = SamLayerVersion("foo") + layer.ContentUri = "s3://foobar/foo.zip" + invalid_architectures = [["arm"], [1], "arm", 1, True] + for architecturea in invalid_architectures: + layer.CompatibleArchitectures = architecturea + with pytest.raises(InvalidResourceException): + layer.to_cloudformation(**self.kwargs) diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index f29cb4d47..99197de9c 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 615fabec3..7d93d3294 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) @@ -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): @@ -1181,6 +1293,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/api_request_model_with_validator.yaml b/tests/translator/input/api_request_model_with_validator.yaml new file mode 100644 index 000000000..8c0f0629b --- /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 000000000..a7c287849 --- /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/api_with_any_method_in_swagger.yaml b/tests/translator/input/api_with_any_method_in_swagger.yaml new file mode 100644 index 000000000..bd6b30474 --- /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/input/api_with_auth_all_minimum.yaml b/tests/translator/input/api_with_auth_all_minimum.yaml index 399df7612..5066b20d9 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 000000000..2afc3d679 --- /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/input/basic_layer.yaml b/tests/translator/input/basic_layer.yaml index 170af09b1..fa9b5f633 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' @@ -33,6 +38,26 @@ Resources: LayerWithCondition: Type: 'AWS::Serverless::LayerVersion' - Condition: TestCondition + Condition: TestCondition + Properties: + 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 + ContentUri: s3://sam-demo-bucket/layer.zip + RetentionPolicy: !Ref DeletePolicy + + LayerWithArchitectures: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + CompatibleArchitectures: + - x86_64 + - arm64 diff --git a/tests/translator/input/cloudwatchevent_intrinsics.yaml b/tests/translator/input/cloudwatchevent_intrinsics.yaml new file mode 100644 index 000000000..57d0c5893 --- /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/input/cognito_userpool_with_event.yaml b/tests/translator/input/cognito_userpool_with_event.yaml index 97cf8a597..bfdd1401a 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_api_invalid_source_vpc_blacklist.yaml b/tests/translator/input/error_api_invalid_source_vpc_blacklist.yaml new file mode 100644 index 000000000..27eadd1fe --- /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 000000000..99ba49f2d --- /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/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 000000000..0d655e85e --- /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 000000000..48ae1fdc3 --- /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/input/error_cognito_trigger_invalid_type.yaml b/tests/translator/input/error_cognito_trigger_invalid_type.yaml new file mode 100644 index 000000000..0f1fb9d24 --- /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/input/error_function_invalid_s3_event.yaml b/tests/translator/input/error_function_invalid_s3_event.yaml new file mode 100644 index 000000000..3fd48f82a --- /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/error_function_with_multiple_architectures.yaml b/tests/translator/input/error_function_with_multiple_architectures.yaml new file mode 100644 index 000000000..389547257 --- /dev/null +++ b/tests/translator/input/error_function_with_multiple_architectures.yaml @@ -0,0 +1,13 @@ +AWSTemplateFormatVersion: '2010-09-09' +Parameters: {} +Resources: + TestFunc: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Description: Created by SAM + Handler: index.handler + Architectures: ["arm64", "x86_64"] + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 diff --git a/tests/translator/input/error_function_with_unknown_architectures.yaml b/tests/translator/input/error_function_with_unknown_architectures.yaml new file mode 100644 index 000000000..2c21fc0dd --- /dev/null +++ b/tests/translator/input/error_function_with_unknown_architectures.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Parameters: {} +Resources: + TestFunc: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Description: Created by SAM + Handler: index.handler + Architectures: ["arm6"] + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 + + TestFunc2: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Description: Created by SAM + Handler: index.handler + Architectures: "arm6" + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 diff --git a/tests/translator/input/error_layer_invalid_properties.yaml b/tests/translator/input/error_layer_invalid_properties.yaml index 606530d6a..dee051fc3 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,22 @@ 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] + + LayerWithStringArchitecture: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + CompatibleArchitectures: "it must be a list" + + LayerWithWrongArchitecture: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + CompatibleArchitectures: ['wrong'] diff --git a/tests/translator/input/error_sns_intrinsics.yaml b/tests/translator/input/error_sns_intrinsics.yaml new file mode 100644 index 000000000..668a0aae8 --- /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/function_with_architectures.yaml b/tests/translator/input/function_with_architectures.yaml new file mode 100644 index 000000000..81be24492 --- /dev/null +++ b/tests/translator/input/function_with_architectures.yaml @@ -0,0 +1,16 @@ +# File: sam.yml +# Version: 0.9 + +AWSTemplateFormatVersion: '2010-09-09' +Parameters: {} +Resources: + TestFunc: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Description: Created by SAM + Handler: index.handler + Architectures: ["arm64"] + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 diff --git a/tests/translator/input/function_with_intrinsic_architecture.yaml b/tests/translator/input/function_with_intrinsic_architecture.yaml new file mode 100644 index 000000000..fff593ab9 --- /dev/null +++ b/tests/translator/input/function_with_intrinsic_architecture.yaml @@ -0,0 +1,16 @@ +Parameters: + ArchitectureRef: + Type: String + Default: 'arm64' +Resources: + FunctionWithArchitecturesIntrinsic: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Description: Created by SAM + Handler: index.handler + Architectures: + Ref: ArchitectureRef + MemorySize: 1024 + Runtime: nodejs12.x + Timeout: 3 \ No newline at end of file diff --git a/tests/translator/input/function_with_request_parameters.yaml b/tests/translator/input/function_with_request_parameters.yaml index e77a7c4a8..2875457c5 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/input/globals_for_function.yaml b/tests/translator/input/globals_for_function.yaml index d16829df4..bc3d802aa 100644 --- a/tests/translator/input/globals_for_function.yaml +++ b/tests/translator/input/globals_for_function.yaml @@ -22,6 +22,8 @@ Globals: Layers: - !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:layer:MyLayer:1 ReservedConcurrentExecutions: 50 + Architectures: + - x86_64 Resources: MinimalFunction: diff --git a/tests/translator/input/kinesis_intrinsics.yaml b/tests/translator/input/kinesis_intrinsics.yaml new file mode 100644 index 000000000..7107cb06a --- /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/input/layers_with_intrinsics.yaml b/tests/translator/input/layers_with_intrinsics.yaml index 21afcde88..28a4e2302 100644 --- a/tests/translator/input/layers_with_intrinsics.yaml +++ b/tests/translator/input/layers_with_intrinsics.yaml @@ -1,4 +1,10 @@ Parameters: + CompatibleArchitecturesList: + Type: CommaDelimitedList + Default: arm64,x86_64 + CompatibleArchitecturesRef: + Type: String + Default: 'arm64' LayerNameParam: Type: String Default: SomeLayerName @@ -64,4 +70,17 @@ Resources: ContentUri: s3://sam-demo-bucket/layer.zip LayerName: !Sub 'layer-${AWS::Region}' + LayerWithArchitecturesIntrinsic: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + CompatibleArchitectures: + Ref: CompatibleArchitecturesList + + LayerWithSingleListRefArchitecturesIntrinsic: + Type: 'AWS::Serverless::LayerVersion' + Properties: + ContentUri: s3://sam-demo-bucket/layer.zip + CompatibleArchitectures: + - Ref: CompatibleArchitecturesRef diff --git a/tests/translator/input/s3_intrinsics.yaml b/tests/translator/input/s3_intrinsics.yaml new file mode 100644 index 000000000..dff8fe980 --- /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/input/sns_intrinsics.yaml b/tests/translator/input/sns_intrinsics.yaml new file mode 100644 index 000000000..cdafe672c --- /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/api_request_model_with_validator.json b/tests/translator/output/api_request_model_with_validator.json new file mode 100644 index 000000000..403404c80 --- /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 000000000..2e52f132f --- /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/api_with_any_method_in_swagger.json b/tests/translator/output/api_with_any_method_in_swagger.json new file mode 100644 index 000000000..c4f81f32b --- /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/api_with_auth_all_minimum.json b/tests/translator/output/api_with_auth_all_minimum.json index 186f6c50d..a64255ce5 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 000000000..32c8b8eaa --- /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_request_model_with_validator.json b/tests/translator/output/aws-cn/api_request_model_with_validator.json new file mode 100644 index 000000000..c392a2353 --- /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 000000000..57bf8cf6e --- /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-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 000000000..44e182380 --- /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-cn/api_with_auth_all_minimum.json b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json index b9e408189..b90828c4a 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 000000000..84b61b86c --- /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-cn/basic_layer.json b/tests/translator/output/aws-cn/basic_layer.json index d373bd19c..f7e9238f5 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,43 @@ }, "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" + } + }, + "LayerWithArchitecturesab56a78493": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "CompatibleArchitectures": [ + "x86_64", + "arm64" + ], + "LayerName": "LayerWithArchitectures" + } } } -} \ 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 000000000..d761fc5e0 --- /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-cn/cognito_userpool_with_event.json b/tests/translator/output/aws-cn/cognito_userpool_with_event.json index 973838a75..9a9faea03 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-cn/function_with_architectures.json b/tests/translator/output/aws-cn/function_with_architectures.json new file mode 100644 index 000000000..7e9b898cf --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_architectures.json @@ -0,0 +1,65 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {}, + "Resources": { + "TestFunc": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "TestFuncRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 3, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Architectures": [ + "arm64" + ] + } + }, + "TestFuncRole": { + "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" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_intrinsic_architecture.json b/tests/translator/output/aws-cn/function_with_intrinsic_architecture.json new file mode 100644 index 000000000..798b131fc --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_intrinsic_architecture.json @@ -0,0 +1,69 @@ +{ + "Parameters": { + "ArchitectureRef": { + "Type": "String", + "Default": "arm64" + } + }, + "Resources": { + "FunctionWithArchitecturesIntrinsic": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "FunctionWithArchitecturesIntrinsicRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 3, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Architectures": { + "Ref": "ArchitectureRef" + } + } + }, + "FunctionWithArchitecturesIntrinsicRole": { + "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" + } + ] + } + } + } +} \ No newline at end of file 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 9cef0cf7d..d5715a354 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-cn/globals_for_function.json b/tests/translator/output/aws-cn/globals_for_function.json index 79ecc2ab9..dd8cad638 100644 --- a/tests/translator/output/aws-cn/globals_for_function.json +++ b/tests/translator/output/aws-cn/globals_for_function.json @@ -83,6 +83,7 @@ } ], "ReservedConcurrentExecutions": 100, + "Architectures": ["x86_64"], "MemorySize": 512, "Environment": { "Variables": { @@ -188,6 +189,7 @@ } ], "ReservedConcurrentExecutions": 50, + "Architectures": ["x86_64"], "MemorySize": 1024, "Environment": { "Variables": { @@ -240,4 +242,4 @@ } } } -} \ No newline at end of file +} 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 000000000..0484d1fdc --- /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-cn/layers_with_intrinsics.json b/tests/translator/output/aws-cn/layers_with_intrinsics.json index bc999791a..bd7cd1a9f 100644 --- a/tests/translator/output/aws-cn/layers_with_intrinsics.json +++ b/tests/translator/output/aws-cn/layers_with_intrinsics.json @@ -1,5 +1,13 @@ { "Parameters": { + "CompatibleArchitecturesList": { + "Type": "CommaDelimitedList", + "Default": "arm64,x86_64" + }, + "CompatibleArchitecturesRef": { + "Type": "String", + "Default": "arm64" + }, "LayerLicenseInfo": { "Default": "MIT-0 License", "Type": "String" @@ -123,6 +131,36 @@ "Fn::Sub": "layer-SomeLayerName" } } + }, + "LayerWithArchitecturesIntrinsicb79067ee69": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithArchitecturesIntrinsic", + "CompatibleArchitectures": { + "Ref": "CompatibleArchitecturesList" + } + } + }, + "LayerWithSingleListRefArchitecturesIntrinsicf8a2807636": { + "DeletionPolicy": "Retain", + "Properties": { + "CompatibleArchitectures": [ + { + "Ref": "CompatibleArchitecturesRef" + } + ], + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithSingleListRefArchitecturesIntrinsic" + }, + "Type": "AWS::Lambda::LayerVersion" } } } 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 000000000..834251134 --- /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-cn/sns_intrinsics.json b/tests/translator/output/aws-cn/sns_intrinsics.json new file mode 100644 index 000000000..b65f7eddf --- /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/api_request_model_with_validator.json b/tests/translator/output/aws-us-gov/api_request_model_with_validator.json new file mode 100644 index 000000000..e7a1e3218 --- /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 000000000..83fd01813 --- /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/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 000000000..9bd62ca94 --- /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/output/aws-us-gov/api_with_auth_all_minimum.json b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json index 9b583ee6f..16b4cfc02 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 000000000..098ebbf10 --- /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/output/aws-us-gov/basic_layer.json b/tests/translator/output/aws-us-gov/basic_layer.json index d373bd19c..28754de47 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,43 @@ }, "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" + } + }, + "LayerWithArchitecturesab56a78493": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "CompatibleArchitectures": [ + "x86_64", + "arm64" + ], + "LayerName": "LayerWithArchitectures" + } } } -} \ 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 000000000..c8d97aea6 --- /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/aws-us-gov/cognito_userpool_with_event.json b/tests/translator/output/aws-us-gov/cognito_userpool_with_event.json index 82a183830..235b0084d 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/aws-us-gov/function_with_architectures.json b/tests/translator/output/aws-us-gov/function_with_architectures.json new file mode 100644 index 000000000..b4069dad5 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_architectures.json @@ -0,0 +1,65 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {}, + "Resources": { + "TestFunc": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "TestFuncRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 3, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Architectures": [ + "arm64" + ] + } + }, + "TestFuncRole": { + "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" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_intrinsic_architecture.json b/tests/translator/output/aws-us-gov/function_with_intrinsic_architecture.json new file mode 100644 index 000000000..cbb241dc9 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_intrinsic_architecture.json @@ -0,0 +1,69 @@ +{ + "Parameters": { + "ArchitectureRef": { + "Type": "String", + "Default": "arm64" + } + }, + "Resources": { + "FunctionWithArchitecturesIntrinsic": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "FunctionWithArchitecturesIntrinsicRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 3, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Architectures": { + "Ref": "ArchitectureRef" + } + } + }, + "FunctionWithArchitecturesIntrinsicRole": { + "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" + } + ] + } + } + } +} \ No newline at end of file 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 44ff1d8f5..67345e559 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/aws-us-gov/globals_for_function.json b/tests/translator/output/aws-us-gov/globals_for_function.json index 6a7fbfe70..49abeafa7 100644 --- a/tests/translator/output/aws-us-gov/globals_for_function.json +++ b/tests/translator/output/aws-us-gov/globals_for_function.json @@ -83,6 +83,7 @@ } ], "ReservedConcurrentExecutions": 100, + "Architectures": ["x86_64"], "MemorySize": 512, "Environment": { "Variables": { @@ -188,6 +189,7 @@ } ], "ReservedConcurrentExecutions": 50, + "Architectures": ["x86_64"], "MemorySize": 1024, "Environment": { "Variables": { @@ -240,4 +242,4 @@ } } } -} \ 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 000000000..a3490c279 --- /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/aws-us-gov/layers_with_intrinsics.json b/tests/translator/output/aws-us-gov/layers_with_intrinsics.json index e7898aa83..48de0c385 100644 --- a/tests/translator/output/aws-us-gov/layers_with_intrinsics.json +++ b/tests/translator/output/aws-us-gov/layers_with_intrinsics.json @@ -1,5 +1,13 @@ { "Parameters": { + "CompatibleArchitecturesList": { + "Type": "CommaDelimitedList", + "Default": "arm64,x86_64" + }, + "CompatibleArchitecturesRef": { + "Type": "String", + "Default": "arm64" + }, "LayerLicenseInfo": { "Default": "MIT-0 License", "Type": "String" @@ -123,6 +131,36 @@ "Fn::Sub": "layer-SomeLayerName" } } + }, + "LayerWithArchitecturesIntrinsicb79067ee69": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithArchitecturesIntrinsic", + "CompatibleArchitectures": { + "Ref": "CompatibleArchitecturesList" + } + } + }, + "LayerWithSingleListRefArchitecturesIntrinsicf8a2807636": { + "DeletionPolicy": "Retain", + "Properties": { + "CompatibleArchitectures": [ + { + "Ref": "CompatibleArchitecturesRef" + } + ], + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithSingleListRefArchitecturesIntrinsic" + }, + "Type": "AWS::Lambda::LayerVersion" } } } 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 000000000..947ec4ef8 --- /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/aws-us-gov/sns_intrinsics.json b/tests/translator/output/aws-us-gov/sns_intrinsics.json new file mode 100644 index 000000000..0df391edf --- /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/basic_layer.json b/tests/translator/output/basic_layer.json index d373bd19c..44c1be560 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,43 @@ }, "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" + } + }, + "LayerWithArchitecturesab56a78493": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "CompatibleArchitectures": [ + "x86_64", + "arm64" + ], + "LayerName": "LayerWithArchitectures" + } } } -} \ 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 000000000..1a12bb785 --- /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/output/cognito_userpool_with_event.json b/tests/translator/output/cognito_userpool_with_event.json index 277f38c37..956742b7f 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_api_invalid_source_vpc_blacklist.json b/tests/translator/output/error_api_invalid_source_vpc_blacklist.json new file mode 100644 index 000000000..cc7d2188a --- /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 000000000..5ce6cf3fd --- /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" +} 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 000000000..c51ae4c45 --- /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 000000000..92b765259 --- /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/output/error_cognito_trigger_invalid_type.json b/tests/translator/output/error_cognito_trigger_invalid_type.json new file mode 100644 index 000000000..1a9bbdd38 --- /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/output/error_function_invalid_s3_event.json b/tests/translator/output/error_function_invalid_s3_event.json new file mode 100644 index 000000000..d76259073 --- /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/error_function_with_multiple_architectures.json b/tests/translator/output/error_function_with_multiple_architectures.json new file mode 100644 index 000000000..d266cb94a --- /dev/null +++ b/tests/translator/output/error_function_with_multiple_architectures.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TestFunc] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`." +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_unknown_architectures.json b/tests/translator/output/error_function_with_unknown_architectures.json new file mode 100644 index 000000000..56ca04788 --- /dev/null +++ b/tests/translator/output/error_function_with_unknown_architectures.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 2. Resource with id [TestFunc] is invalid. Architectures needs to be a list with one string, either `x86_64` or `arm64`. Resource with id [TestFunc2] is invalid. Type of property 'Architectures' is invalid." +} \ No newline at end of file diff --git a/tests/translator/output/error_globals_unsupported_property.json b/tests/translator/output/error_globals_unsupported_property.json index 2b2dfbf18..706b2aabd 100644 --- a/tests/translator/output/error_globals_unsupported_property.json +++ b/tests/translator/output/error_globals_unsupported_property.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn']" + "errorMessage": "'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures']" } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn']" -} + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. 'Globals' section is invalid. 'SomeKey' is not a supported property of 'Function'. Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'Layers', 'DeploymentPreference', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures']" +} \ 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 696ca48b0..f064188ae 100644 --- a/tests/translator/output/error_layer_invalid_properties.json +++ b/tests/translator/output/error_layer_invalid_properties.json @@ -1,8 +1,8 @@ { "errors": [ { - "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": "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. Resource with id [LayerWithStringArchitecture] is invalid. Type of property 'CompatibleArchitectures' is invalid. Type of property 'CompatibleRuntimes' is invalid. Resource with id [LayerWithWrongArchitecture5b58896c5a] is invalid. CompatibleArchitectures needs to be a list of 'x86_64' or 'arm64'" } ], - "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." - } \ No newline at end of file + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 9. 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. Resource with id [LayerWithStringArchitecture] is invalid. Type of property 'CompatibleArchitectures' is invalid. Resource with id [LayerWithWrongArchitecture5b58896c5a] is invalid. CompatibleArchitectures needs to be a list of 'x86_64' or 'arm64'" + } diff --git a/tests/translator/output/error_sns_intrinsics.json b/tests/translator/output/error_sns_intrinsics.json new file mode 100644 index 000000000..ad95a1a66 --- /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/function_with_architectures.json b/tests/translator/output/function_with_architectures.json new file mode 100644 index 000000000..22628a816 --- /dev/null +++ b/tests/translator/output/function_with_architectures.json @@ -0,0 +1,65 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Parameters": {}, + "Resources": { + "TestFuncRole": { + "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": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + } + } + }, + "TestFunc": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "MemorySize": 1024, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "TestFuncRole", + "Arn" + ] + }, + "Timeout": 3, + "Runtime": "nodejs12.x", + "Architectures": [ + "arm64" + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_intrinsic_architecture.json b/tests/translator/output/function_with_intrinsic_architecture.json new file mode 100644 index 000000000..64c543682 --- /dev/null +++ b/tests/translator/output/function_with_intrinsic_architecture.json @@ -0,0 +1,69 @@ +{ + "Parameters": { + "ArchitectureRef": { + "Type": "String", + "Default": "arm64" + } + }, + "Resources": { + "FunctionWithArchitecturesIntrinsic": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Description": "Created by SAM", + "Handler": "index.handler", + "MemorySize": 1024, + "Role": { + "Fn::GetAtt": [ + "FunctionWithArchitecturesIntrinsicRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x", + "Timeout": 3, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "Architectures": { + "Ref": "ArchitectureRef" + } + } + }, + "FunctionWithArchitecturesIntrinsicRole": { + "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" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_request_parameters.json b/tests/translator/output/function_with_request_parameters.json index 51dcff6ba..940a79710 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 diff --git a/tests/translator/output/globals_for_function.json b/tests/translator/output/globals_for_function.json index 6174a06d0..f922869fb 100644 --- a/tests/translator/output/globals_for_function.json +++ b/tests/translator/output/globals_for_function.json @@ -83,6 +83,7 @@ } ], "ReservedConcurrentExecutions": 100, + "Architectures": ["x86_64"], "MemorySize": 512, "Environment": { "Variables": { @@ -188,6 +189,7 @@ } ], "ReservedConcurrentExecutions": 50, + "Architectures": ["x86_64"], "MemorySize": 1024, "Environment": { "Variables": { @@ -240,4 +242,4 @@ } } } -} \ 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 000000000..c7802de79 --- /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/output/layers_with_intrinsics.json b/tests/translator/output/layers_with_intrinsics.json index d8bb81b37..f1b63d8a0 100644 --- a/tests/translator/output/layers_with_intrinsics.json +++ b/tests/translator/output/layers_with_intrinsics.json @@ -1,5 +1,13 @@ { "Parameters": { + "CompatibleArchitecturesList": { + "Type": "CommaDelimitedList", + "Default": "arm64,x86_64" + }, + "CompatibleArchitecturesRef": { + "Type": "String", + "Default": "arm64" + }, "LayerLicenseInfo": { "Default": "MIT-0 License", "Type": "String" @@ -123,6 +131,36 @@ "Fn::Sub": "layer-SomeLayerName" } } + }, + "LayerWithArchitecturesIntrinsicb79067ee69": { + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::LayerVersion", + "Properties": { + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithArchitecturesIntrinsic", + "CompatibleArchitectures": { + "Ref": "CompatibleArchitecturesList" + } + } + }, + "LayerWithSingleListRefArchitecturesIntrinsicf8a2807636": { + "DeletionPolicy": "Retain", + "Properties": { + "CompatibleArchitectures": [ + { + "Ref": "CompatibleArchitecturesRef" + } + ], + "Content": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "layer.zip" + }, + "LayerName": "LayerWithSingleListRefArchitecturesIntrinsic" + }, + "Type": "AWS::Lambda::LayerVersion" } } } diff --git a/tests/translator/output/s3_intrinsics.json b/tests/translator/output/s3_intrinsics.json new file mode 100644 index 000000000..6b5e52f3e --- /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/output/sns_intrinsics.json b/tests/translator/output/sns_intrinsics.json new file mode 100644 index 000000000..c591b0266 --- /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_arn_generator.py b/tests/translator/test_arn_generator.py index 461840cdf..200f90414 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 diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index ba244dd0b..f3a304c9c 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", @@ -315,11 +316,14 @@ 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", "api_with_canary_setting", "api_with_xray_tracing", "api_request_model", + "api_request_model_with_validator", "api_with_stage_tags", "api_with_mode", "s3", @@ -327,18 +331,21 @@ 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", "sns", "sns_sqs", "sns_existing_sqs", + "sns_intrinsics", "sns_outside_sqs", "sns_existing_other_subscription", "sns_topic_outside_template", "alexa_skill", "alexa_skill_with_skill_id", "iot_rule", + "kinesis_intrinsics", "layers_with_intrinsics", "layers_all_properties", "layer_deletion_policy_precedence", @@ -438,6 +445,8 @@ class TestTranslatorEndToEnd(AbstractTestTranslator): "function_with_file_system_config", "state_machine_with_permissions_boundary", "version_deletion_policy_precedence", + "function_with_architectures", + "function_with_intrinsic_architecture", ], [ ("aws", "ap-southeast-1"), @@ -472,6 +481,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", @@ -662,12 +672,15 @@ 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", "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", @@ -688,6 +701,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", @@ -718,6 +732,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", @@ -771,6 +786,8 @@ def test_transform_success_no_side_effect(self, testcase, partition_with_region) "error_invalid_method_definition", "error_mappings_is_null", "error_swagger_security_not_dict", + "error_function_with_unknown_architectures", + "error_function_with_multiple_architectures", ], ) @patch("boto3.session.Session.region_name", "ap-southeast-1") diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index fd020a5db..3794356fe 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -136,6 +136,7 @@ VersionDescription | `string` | A string that specifies the Description field wh ReservedConcurrentExecutions | `integer` | The maximum of concurrent executions you want to reserve for the function. For more information see [AWS Documentation on managing concurrency](https://docs.aws.amazon.com/lambda/latest/dg/concurrent-executions.html) ProvisionedConcurrencyConfig | [ProvisionedConcurrencyConfig Object](#provisioned-concurrency-config-object) | Configure provisioned capacity for a number of concurrent executions on Lambda Alias property. EventInvokeConfig | [EventInvokeConfig object](#event-invoke-config-object) | Configure options for [asynchronous invocation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-async.html) on the function. +Architectures | List of `string` | The CPU architecture to run on (x86_64 or arm64), accepts only one value. Defaults to x86_64. ##### Return values @@ -397,6 +398,7 @@ Property Name | Type | Description LayerName | `string` | Name of this layer. If you don't specify a name, the logical id of the resource will be used as the name. Description | `string` | Description of this layer. ContentUri | `string` | [S3 Location Object](#s3-location-object) | **Required.** S3 Uri or location for the layer code. +CompatibleArchitectures | List of `string` | List or architectures compatibles with this LayerVersion. CompatibleRuntimes | List of `string`| List of runtimes compatible with this LayerVersion. LicenseInfo | `string` | Information about the license for this LayerVersion. RetentionPolicy | `string` | Options are `Retain` and `Delete`. Defaults to `Retain`. When `Retain` is set, SAM adds `DeletionPolicy: Retain` to the transformed resource so CloudFormation does not delete old versions after an update.