diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py index 82af15d1d..7b054c0e0 100644 --- a/integration/helpers/base_test.py +++ b/integration/helpers/base_test.py @@ -28,6 +28,7 @@ class BaseTest(TestCase): @classmethod 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") @@ -216,6 +217,11 @@ def get_api_v2_stack_stages(self): return self.client_provider.api_v2_client.get_stages(ApiId=resources[0]["PhysicalResourceId"])["Items"] + def get_api_v2_endpoint(self, logical_id): + api_id = self.get_physical_id_by_logical_id(logical_id) + api = self.client_provider.api_v2_client.get_api(ApiId=api_id) + return api["ApiEndpoint"] + def get_stack_nested_stack_resources(self): resources = self.get_stack_resources("AWS::CloudFormation::Stack") diff --git a/integration/resources/code/code.zip b/integration/resources/code/code.zip index 3f21de8b6..cf321b596 100644 Binary files a/integration/resources/code/code.zip and b/integration/resources/code/code.zip differ diff --git a/integration/resources/expected/single/function_alias_with_http_api_events.json b/integration/resources/expected/single/function_alias_with_http_api_events.json new file mode 100644 index 000000000..8001cf476 --- /dev/null +++ b/integration/resources/expected/single/function_alias_with_http_api_events.json @@ -0,0 +1,10 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionAliaslive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyLambdaFunctionFooEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionBarEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyHttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyHttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] diff --git a/integration/resources/expected/single/function_with_http_api_events.json b/integration/resources/expected/single/function_with_http_api_events.json new file mode 100644 index 000000000..14e65d310 --- /dev/null +++ b/integration/resources/expected/single/function_with_http_api_events.json @@ -0,0 +1,7 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionFooEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyHttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"MyHttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] diff --git a/integration/resources/templates/single/function_alias_with_http_api_events.yaml b/integration/resources/templates/single/function_alias_with_http_api_events.yaml new file mode 100644 index 000000000..88176be07 --- /dev/null +++ b/integration/resources/templates/single/function_alias_with_http_api_events.yaml @@ -0,0 +1,23 @@ +Resources: + MyHttpApi: + Type: AWS::Serverless::HttpApi + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + AutoPublishAlias: live + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Events: + FooEvent: + Type: HttpApi + Properties: + ApiId: + Ref: MyHttpApi + BarEvent: + Type: HttpApi + Properties: + ApiId: + Ref: MyHttpApi + Path: /bar + Method: POST diff --git a/integration/resources/templates/single/function_with_http_api_events.yaml b/integration/resources/templates/single/function_with_http_api_events.yaml new file mode 100644 index 000000000..1ffbe05f7 --- /dev/null +++ b/integration/resources/templates/single/function_with_http_api_events.yaml @@ -0,0 +1,22 @@ +Resources: + MyHttpApi: + Type: AWS::Serverless::HttpApi + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + Events: + FooEvent: + Type: HttpApi + Properties: + ApiId: + Ref: MyHttpApi + BarEvent: + Type: HttpApi + Properties: + ApiId: + Ref: MyHttpApi + Path: /bar + Method: POST diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index 376359647..992a44245 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -1,3 +1,4 @@ +import requests from parameterized import parameterized from integration.helpers.base_test import BaseTest @@ -26,6 +27,19 @@ def test_basic_function(self, file_name): self.assertEqual(self.get_resource_status_by_logical_id("MyLambdaFunction"), "UPDATE_COMPLETE") + @parameterized.expand( + [ + "function_with_http_api_events", + "function_alias_with_http_api_events", + ] + ) + def test_function_with_http_api_events(self, file_name): + self.create_and_verify_stack(file_name) + + endpoint = self.get_api_v2_endpoint("MyHttpApi") + + self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT) + @parameterized.expand( [ ("basic_function_with_sns_dlq", "sns:Publish"), diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 230bd86c5..fc0f77a26 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -102,7 +102,14 @@ def get_integration_function_logical_id(self, path_name, method_name): # Extract lambda integration (${LambdaName.Arn}) and split ".Arn" off from it regex = "([A-Za-z0-9]+\.Arn)" - match = re.findall(regex, arn)[0].split(".Arn")[0] + matches = re.findall(regex, arn) + # Prevent IndexError when integration URI doesn't contain .Arn (e.g. a Function with + # AutoPublishAlias translates to AWS::Lambda::Alias, which make_shorthand represents + # as LogicalId instead of LogicalId.Arn). + # TODO: Consistent handling of Functions with and without AutoPublishAlias (see #1901) + if not matches: + return False + match = matches[0].split(".Arn")[0] return match def method_has_integration(self, method): diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index 9938cab1c..f29cb4d47 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -437,3 +437,50 @@ def test_must_not_add_description_if_already_defined(self): editor = OpenApiEditor(self.original_openapi_with_description) editor.add_description("New Description") self.assertEqual(editor.openapi["info"]["description"], "Existing Description") + + +class TestOpenApiEditor_get_integration_function_of_alias(TestCase): + def setUp(self): + + self.original_openapi = { + "openapi": "3.0.1", + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "Fn::If": [ + "condition", + { + "security": [{"OpenIdAuth": ["scope1", "scope2"]}], + "isDefaultRoute": True, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "condition", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunctionAlias}/invocations" + }, + {"Ref": "AWS::NoValue"}, + ] + }, + "payloadFormatVersion": "1.0", + }, + "responses": {}, + }, + {"Ref": "AWS::NoValue"}, + ] + } + }, + "/bar": {}, + "/badpath": "string value", + }, + } + + self.editor = OpenApiEditor(self.original_openapi) + + def test_no_logical_id_if_alias(self): + + self.assertFalse( + self.editor.get_integration_function_logical_id(OpenApiEditor._DEFAULT_PATH, OpenApiEditor._X_ANY_METHOD), + )