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..7a9fd9574 --- /dev/null +++ b/integration/combination/test_api_settings.py @@ -0,0 +1,179 @@ +from io import BytesIO + +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path + +import requests +from parameterized import parameterized + +from integration.helpers.base_test import BaseTest + +from PIL import Image + + +class TestApiSettings(BaseTest): + def test_method_settings(self): + self.create_and_verify_stack("combination/api_with_method_settings") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + + wildcard_path = "*/*" + + method_settings = response["methodSettings"] + self.assertTrue(wildcard_path in method_settings, "MethodSettings for the wildcard path must be present") + + wildcard_path_setting = method_settings[wildcard_path] + + self.assertTrue(wildcard_path_setting["metricsEnabled"], "Metrics must be enabled") + self.assertTrue(wildcard_path_setting["dataTraceEnabled"], "DataTrace must be enabled") + self.assertEqual(wildcard_path_setting["loggingLevel"], "INFO", "LoggingLevel must be INFO") + + @parameterized.expand( + [ + "combination/api_with_binary_media_types", + "combination/api_with_binary_media_types_with_definition_body", + ] + ) + def test_binary_media_types(self, file_name): + self.create_and_verify_stack(file_name, self.get_default_test_template_parameters()) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + self.assertEqual(set(response["binaryMediaTypes"]), {"image/jpg", "image/png", "image/gif"}) + + @parameterized.expand( + [ + "combination/api_with_request_models", + "combination/api_with_request_models_openapi", + ] + ) + def test_request_models(self, file_name): + self.create_and_verify_stack(file_name) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_models(restApiId=rest_api_id) + request_models = response["items"] + + self.assertEqual(request_models[0]["name"], "user") + self.assertEqual( + request_models[0]["schema"], + '{\n "type" : "object",\n' + + ' "properties" : {\n "username" : {\n "type" : "string"\n }\n' + + " }\n}", + ) + + def test_request_parameters_open_api(self): + self.create_and_verify_stack("combination/api_with_request_parameters_openapi") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + # Test if the request parameters got set on the method + resources_response = apigw_client.get_resources(restApiId=rest_api_id) + resources = resources_response["items"] + + resource = get_resource_by_path(resources, "/one") + method = apigw_client.get_method(restApiId=rest_api_id, resourceId=resource["id"], httpMethod="GET") + expected = {"method.request.querystring.type": True} + self.assertEqual(expected, method["requestParameters"]) + + # Test that the method settings got applied on the method + stage_response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + method_settings = stage_response["methodSettings"] + + path = "one/GET" + self.assertTrue(path in method_settings, "MethodSettings for the path must be present") + + path_settings = method_settings[path] + self.assertEqual(path_settings["cacheTtlInSeconds"], 15) + self.assertTrue(path_settings["cachingEnabled"], "Caching must be enabled") + + def test_binary_media_types_with_definition_body_openapi(self): + parameters = self.get_default_test_template_parameters() + binary_media = { + "ParameterKey": "BinaryMediaCodeKey", + "ParameterValue": "binary-media.zip", + "UsePreviousValue": False, + "ResolvedValue": "string", + } + parameters.append(binary_media) + + self.create_and_verify_stack("combination/api_with_binary_media_types_with_definition_body_openapi", parameters) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + self.assertEqual( + set(response["binaryMediaTypes"]), {"image/jpg", "image/png", "image/gif", "application/octet-stream"} + ) + base_url = self.get_stack_output("ApiUrl")["OutputValue"] + self.verify_binary_media_request(base_url + "none", 200) + + @parameterized.expand( + [ + "combination/api_with_endpoint_configuration", + "combination/api_with_endpoint_configuration_dict", + ] + ) + def test_end_point_configuration(self, file_name): + self.create_and_verify_stack(file_name, self.get_default_test_template_parameters()) + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + endpoint_config = response["endpointConfiguration"] + self.assertEqual(endpoint_config["types"], ["REGIONAL"]) + + def test_implicit_api_settings(self): + self.create_and_verify_stack("combination/implicit_api_with_settings") + + rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi") + apigw_client = self.client_provider.api_client + + response = apigw_client.get_stage(restApiId=rest_api_id, stageName="Prod") + + wildcard_path = "*/*" + + method_settings = response["methodSettings"] + self.assertTrue(wildcard_path in method_settings, "MethodSettings for the wildcard path must be present") + + wildcard_path_setting = method_settings[wildcard_path] + + self.assertTrue(wildcard_path_setting["metricsEnabled"], "Metrics must be enabled") + self.assertTrue(wildcard_path_setting["dataTraceEnabled"], "DataTrace must be enabled") + self.assertEqual(wildcard_path_setting["loggingLevel"], "INFO", "LoggingLevel must be INFO") + + response = apigw_client.get_rest_api(restApiId=rest_api_id) + endpoint_config = response["endpointConfiguration"] + self.assertEqual(endpoint_config["types"], ["REGIONAL"]) + self.assertEqual(set(response["binaryMediaTypes"]), {"image/jpg", "image/png"}) + + def verify_binary_media_request(self, url, expected_status_code): + headers = {"accept": "image/png"} + response = requests.get(url, headers=headers) + + status = response.status_code + expected = Image.open(Path(self.code_dir, "AWS_logo_RGB.png")) + + if 200 <= status <= 299: + actual = Image.open(BytesIO(response.content)) + self.assertEqual(expected, actual) + + self.assertEqual(status, expected_status_code, " must return HTTP " + str(expected_status_code)) + + +def get_resource_by_path(resources, path): + for resource in resources: + if resource["path"] == path: + return resource + return None diff --git a/integration/combination/test_api_with_authorizers.py b/integration/combination/test_api_with_authorizers.py new file mode 100644 index 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..16f0adc5d --- /dev/null +++ b/integration/combination/test_function_with_kinesis.py @@ -0,0 +1,24 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithKinesis(BaseTest): + def test_function_with_kinesis_trigger(self): + self.create_and_verify_stack("combination/function_with_kinesis") + + kinesis_client = self.client_provider.kinesis_client + kinesis_id = self.get_physical_id_by_type("AWS::Kinesis::Stream") + kinesis_stream = kinesis_client.describe_stream(StreamName=kinesis_id)["StreamDescription"] + + lambda_client = self.client_provider.lambda_client + function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + lambda_function_arn = lambda_client.get_function_configuration(FunctionName=function_name)["FunctionArn"] + + event_source_mapping_arn = self.get_physical_id_by_type("AWS::Lambda::EventSourceMapping") + event_source_mapping_result = lambda_client.get_event_source_mapping(UUID=event_source_mapping_arn) + event_source_mapping_batch_size = event_source_mapping_result["BatchSize"] + event_source_mapping_function_arn = event_source_mapping_result["FunctionArn"] + event_source_mapping_kinesis_stream_arn = event_source_mapping_result["EventSourceArn"] + + self.assertEqual(event_source_mapping_batch_size, 100) + self.assertEqual(event_source_mapping_function_arn, lambda_function_arn) + self.assertEqual(event_source_mapping_kinesis_stream_arn, kinesis_stream["StreamARN"]) diff --git a/integration/combination/test_function_with_layers.py b/integration/combination/test_function_with_layers.py new file mode 100644 index 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..8e04d41d2 --- /dev/null +++ b/integration/combination/test_function_with_s3_bucket.py @@ -0,0 +1,18 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithS3Bucket(BaseTest): + def test_function_with_s3_bucket_trigger(self): + self.create_and_verify_stack("combination/function_with_s3") + + # Get the notification configuration and make sure Lambda Function connection is added + s3_client = self.client_provider.s3_client + s3_bucket_name = self.get_physical_id_by_type("AWS::S3::Bucket") + configurations = s3_client.get_bucket_notification_configuration(Bucket=s3_bucket_name)[ + "LambdaFunctionConfigurations" + ] + + # There should be only One notification configuration for the event + self.assertEqual(len(configurations), 1) + config = configurations[0] + self.assertEqual(set(config["Events"]), {"s3:ObjectCreated:*"}) diff --git a/integration/combination/test_function_with_schedule.py b/integration/combination/test_function_with_schedule.py new file mode 100644 index 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..fa90c1ee8 --- /dev/null +++ b/integration/combination/test_function_with_sns.py @@ -0,0 +1,28 @@ +from integration.helpers.base_test import BaseTest + + +class TestFunctionWithSns(BaseTest): + def test_function_with_sns_bucket_trigger(self): + self.create_and_verify_stack("combination/function_with_sns") + + sns_client = self.client_provider.sns_client + + sns_topic_arn = self.get_physical_id_by_type("AWS::SNS::Topic") + lambda_function_endpoint = self.get_physical_id_by_type("AWS::Lambda::Function") + + subscriptions = sns_client.list_subscriptions_by_topic(TopicArn=sns_topic_arn)["Subscriptions"] + self.assertEqual(len(subscriptions), 2) + + # checks if SNS has two subscriptions: lambda and SQS + lambda_subscription = next((x for x in subscriptions if x["Protocol"] == "lambda"), None) + + self.assertIsNotNone(lambda_subscription) + self.assertTrue(lambda_function_endpoint in lambda_subscription["Endpoint"]) + self.assertEqual(lambda_subscription["Protocol"], "lambda") + self.assertEqual(lambda_subscription["TopicArn"], sns_topic_arn) + + sqs_subscription = next((x for x in subscriptions if x["Protocol"] == "sqs"), None) + + self.assertIsNotNone(sqs_subscription) + self.assertEqual(sqs_subscription["Protocol"], "sqs") + self.assertEqual(sqs_subscription["TopicArn"], sns_topic_arn) diff --git a/integration/combination/test_function_with_sqs.py b/integration/combination/test_function_with_sqs.py new file mode 100644 index 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/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..a6d6c7d88 100644 --- a/integration/helpers/deployer/deployer.py +++ b/integration/helpers/deployer/deployer.py @@ -43,7 +43,7 @@ MIN_OFFSET, ) from integration.helpers.deployer.utils.artifact_exporter import mktempfile, parse_s3_url -from integration.helpers.deployer.utils.time import utc_to_timestamp +from integration.helpers.deployer.utils.time_util import utc_to_timestamp LOG = logging.getLogger(__name__) diff --git a/integration/helpers/deployer/utils/retry.py b/integration/helpers/deployer/utils/retry.py new file mode 100644 index 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..d1150fc15 100644 --- a/integration/helpers/resource.py +++ b/integration/helpers/resource.py @@ -151,3 +151,23 @@ def current_region_does_not_support(services): # check if any one of the services is in the excluded services for current testing region return bool(set(services).intersection(set(region_exclude_services["regions"][region]))) + + +def first_item_in_dict(dictionary): + """ + return the first key-value pair in dictionary + + Parameters + ---------- + dictionary : Dictionary + the dictionary used to grab the first tiem + + Returns + ------- + Tuple + the first key-value pair in the dictionary + """ + if not dictionary: + return None + first_key = list(dictionary.keys())[0] + return first_key, dictionary[first_key] diff --git a/integration/resources/code/AWS_logo_RGB.png b/integration/resources/code/AWS_logo_RGB.png new file mode 100644 index 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_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_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_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/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_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_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_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/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..11ef5809a 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -14,9 +14,9 @@ class TestBasicFunction(BaseTest): @parameterized.expand( [ - "basic_function", - "basic_function_no_envvar", - "basic_function_openapi", + "single/basic_function", + "single/basic_function_no_envvar", + "single/basic_function_openapi", ] ) def test_basic_function(self, file_name): @@ -33,8 +33,8 @@ def test_basic_function(self, file_name): @parameterized.expand( [ - "function_with_http_api_events", - "function_alias_with_http_api_events", + "single/function_with_http_api_events", + "single/function_alias_with_http_api_events", ] ) def test_function_with_http_api_events(self, file_name): @@ -45,12 +45,12 @@ def test_function_with_http_api_events(self, file_name): self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT) def test_function_with_deployment_preference_alarms_intrinsic_if(self): - self.create_and_verify_stack("function_with_deployment_preference_alarms_intrinsic_if") + self.create_and_verify_stack("single/function_with_deployment_preference_alarms_intrinsic_if") @parameterized.expand( [ - ("basic_function_with_sns_dlq", "sns:Publish"), - ("basic_function_with_sqs_dlq", "sqs:SendMessage"), + ("single/basic_function_with_sns_dlq", "sns:Publish"), + ("single/basic_function_with_sqs_dlq", "sqs:SendMessage"), ] ) def test_basic_function_with_dlq(self, file_name, action): @@ -83,7 +83,7 @@ def test_basic_function_with_kms_key_arn(self): """ Creates a basic lambda function with KMS key arn """ - self.create_and_verify_stack("basic_function_with_kmskeyarn") + self.create_and_verify_stack("single/basic_function_with_kmskeyarn") lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") function_configuration = self.client_provider.lambda_client.get_function_configuration( @@ -97,7 +97,7 @@ def test_basic_function_with_tags(self): """ Creates a basic lambda function with tags """ - self.create_and_verify_stack("basic_function_with_tags") + self.create_and_verify_stack("single/basic_function_with_tags") lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") get_function_result = self.client_provider.lambda_client.get_function(FunctionName=lambda_function_name) tags = get_function_result["Tags"] @@ -114,7 +114,7 @@ def test_basic_function_event_destinations(self): """ Creates a basic lambda function with event destinations """ - self.create_and_verify_stack("basic_function_event_destinations") + self.create_and_verify_stack("single/basic_function_event_destinations") test_function_1 = self.get_physical_id_by_logical_id("MyTestFunction") test_function_2 = self.get_physical_id_by_logical_id("MyTestFunction2") @@ -158,27 +158,7 @@ def test_basic_function_with_tracing(self): """ Creates a basic lambda function with tracing """ - parameters = [ - { - "ParameterKey": "Bucket", - "ParameterValue": self.s3_bucket_name, - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - { - "ParameterKey": "CodeKey", - "ParameterValue": "code.zip", - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - { - "ParameterKey": "SwaggerKey", - "ParameterValue": "swagger1.json", - "UsePreviousValue": False, - "ResolvedValue": "string", - }, - ] - self.create_and_verify_stack("basic_function_with_tracing", parameters) + self.create_and_verify_stack("single/basic_function_with_tracing", self.get_default_test_template_parameters()) active_tracing_function_id = self.get_physical_id_by_logical_id("ActiveTracingFunction") pass_through_tracing_function_id = self.get_physical_id_by_logical_id("PassThroughTracingFunction") diff --git a/integration/single/test_basic_http_api.py b/integration/single/test_basic_http_api.py index 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..de76eab94 100644 --- a/integration/single/test_basic_layer_version.py +++ b/integration/single/test_basic_layer_version.py @@ -14,7 +14,7 @@ def test_basic_layer_version(self): """ Creates a basic lambda layer version """ - self.create_and_verify_stack("basic_layer") + self.create_and_verify_stack("single/basic_layer") layer_logical_id_1 = self.get_logical_id_by_type("AWS::Lambda::LayerVersion") @@ -31,7 +31,7 @@ def test_basic_layer_with_parameters(self): """ Creates a basic lambda layer version with parameters """ - self.create_and_verify_stack("basic_layer_with_parameters") + self.create_and_verify_stack("single/basic_layer_with_parameters") outputs = self.get_stack_outputs() layer_arn = outputs["MyLayerArn"] diff --git a/integration/single/test_basic_state_machine.py b/integration/single/test_basic_state_machine.py index 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 193b157bb..e9f3e9b82 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -15,6 +15,8 @@ parameterized~=0.7.4 pathlib2>=2.3.5; python_version < '3' click~=7.1 dateparser~=0.7 +pillow~=6.2.2 +boto3~=1.17 # Requirements for examples requests~=2.24.0