diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index f4832a9bf3..046d316453 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -247,6 +247,20 @@ class GatewayV2RouteToGatewayV2IntegrationLocalVariablesLinkingLimitationExcepti """ +class OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway V2 Integration linking to more than one Lambda function + """ + + +class GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException( + LocalVariablesLinkingLimitationException +): + """ + Exception specific for Gateway V2 Integration linking to Lambda Function using locals. + """ + + class InvalidSamMetadataPropertiesException(UserException): pass diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 5e32c1a2f0..948e0e1201 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -16,6 +16,7 @@ GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException, GatewayResourceToGatewayRestApiLocalVariablesLinkingLimitationException, + GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, GatewayV2RouteToGatewayV2IntegrationLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, @@ -27,6 +28,7 @@ OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, OneGatewayResourceToRestApiLinkingLimitationException, + OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, OneGatewayV2RouteToGatewayV2IntegrationLinkingLimitationException, OneLambdaFunctionResourceToApiGatewayIntegrationLinkingLimitationException, OneLambdaLayerLinkingLimitationException, @@ -1807,3 +1809,86 @@ def _link_gateway_v2_route_to_integration( tf_destination_value_extractor_from_link_field_value_function=_extract_gateway_v2_integration_id_from_route_target_value, ) ResourceLinker(resource_linking_pair).link_resources() + + +def _link_gateway_v2_integration_to_lambda_function_callback( + gateway_v2_integration_cfn_resource: Dict, lambda_function_resources: List[ReferenceType] +): + """ + Callback function that is used by the linking algorithm to update a CFN V2 Route Resource with a reference to + the Gateway V2 integration resource + + Parameters + ---------- + gateway_v2_integration_cfn_resource: Dict + API Gateway V2 Integration CFN resource + lambda_function_resources: List[ReferenceType] + List of referenced lambda function either as the logical id of Integration resources + defined in the customer project, or the invocation ARN values for actual functions defined + in customer's account. This list should always contain one element only. + """ + if len(lambda_function_resources) > 1: + raise InvalidResourceLinkingException("Could not link multiple lambda functions to one Gateway V2 Integration") + + if not lambda_function_resources: + LOG.info( + "Unable to find any references to Lambda functions, skip linking Gateway V2 Integration to Lambda Functions" + ) + return + + logical_id = lambda_function_resources[0] + gateway_v2_integration_cfn_resource["Properties"]["IntegrationUri"] = ( + { + "Fn::Join": [ + "", + [ + "arn:", + {"Ref": "AWS::Partition"}, + ":apigateway:", + {"Ref": "AWS::Region"}, + ":lambda:path/2015-03-31/functions/", + {"Fn::GetAtt": [logical_id.value, "Arn"]}, + "/invocations", + ], + ] + } + if isinstance(logical_id, LogicalIdReference) + else logical_id.value + ) + + +def _link_gateway_v2_integration_to_lambda_function( + v2_gateway_integration_config_resources: Dict[str, TFResource], + v2_gateway_integration_config_address_cfn_resources_map: Dict[str, List], + lambda_functions_resources: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding + Gateway V2 integration resources to each lambda functions + + Parameters + ---------- + v2_gateway_integration_config_resources: Dict[str, TFResource] + Dictionary of configuration Gateway Integrations + v2_gateway_integration_config_address_cfn_resources_map: Dict[str, List] + Dictionary containing resolved configuration addresses matched up to the cfn Gateway Integration + lambda_functions_resources: Dict[str, Dict] + Dictionary of all Terraform lambda functions resources (not configuration resources). + The dictionary's key is the calculated logical id for each resource. + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, + local_variable_linking_exception=GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=v2_gateway_integration_config_address_cfn_resources_map, + source_resource_tf_config=v2_gateway_integration_config_resources, + destination_resource_tf=lambda_functions_resources, + tf_destination_attribute_name="invoke_arn", + terraform_link_field_name="integration_uri", + cfn_link_field_name="IntegrationUri", + terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_v2_integration_to_lambda_function_callback, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py index 9e010ac342..6b8dd95257 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -26,6 +26,7 @@ _link_gateway_methods_to_gateway_rest_apis, _link_gateway_resources_to_gateway_rest_apis, _link_gateway_stage_to_rest_api, + _link_gateway_v2_integration_to_lambda_function, _link_gateway_v2_route_to_integration, _link_lambda_functions_to_layers, ) @@ -98,4 +99,9 @@ dest=TF_AWS_API_GATEWAY_V2_INTEGRATION, linking_func=_link_gateway_v2_route_to_integration, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_V2_INTEGRATION, + dest=TF_AWS_LAMBDA_FUNCTION, + linking_func=_link_gateway_v2_integration_to_lambda_function, + ), ] diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index a76cf5547e..474013fccc 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -39,6 +39,8 @@ GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, OneGatewayV2RouteToGatewayV2IntegrationLinkingLimitationException, GatewayV2RouteToGatewayV2IntegrationLocalVariablesLinkingLimitationException, + OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, + GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( @@ -86,6 +88,8 @@ _link_gateway_v2_route_to_integration, API_GATEWAY_V2_INTEGRATION_RESOURCE_ADDRESS_PREFIX, _link_gateway_v2_route_to_integration_callback, + _link_gateway_v2_integration_to_lambda_function_callback, + _link_gateway_v2_integration_to_lambda_function, _extract_gateway_v2_integration_id_from_route_target_value, ) from samcli.hook_packages.terraform.hooks.prepare.utilities import get_configuration_address @@ -2118,6 +2122,10 @@ def test_link_gateway_integration_to_function_call_back( _link_gateway_v2_route_to_integration_callback, "Could not link multiple Gateway V2 Integrations to one Gateway V2 Route", ), + ( + _link_gateway_v2_integration_to_lambda_function_callback, + "Could not link multiple lambda functions to one Gateway V2 Integration", + ), ] ) def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message): @@ -2133,6 +2141,7 @@ def test_linking_callbacks_raises_multiple_reference_exception(self, linking_cal (_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back,), (_link_gateway_method_to_gateway_authorizer_call_back,), (_link_gateway_v2_route_to_integration_callback,), + (_link_gateway_v2_integration_to_lambda_function_callback,), ] ) def test_linking_callbacks_skips_empty_references(self, linking_call_back_method): @@ -2474,6 +2483,87 @@ def test__link_gateway_v2_route_to_integration_callback(self, input_gateway_v2_r input_gateway_v2_route["Properties"]["Target"] = expected_route self.assertEqual(gateway_resource, input_gateway_v2_route) + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_v2_integration_to_lambda_function_callback" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_v2_integration_to_lambda_function( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_gateway_v2_integration_to_lambda_function_callback, + ): + integrations_v2_cfn_resources = Mock() + integrations_v2_config_resources = Mock() + lambda_function_tf_resources = Mock() + + _link_gateway_v2_integration_to_lambda_function( + integrations_v2_config_resources, integrations_v2_cfn_resources, lambda_function_tf_resources + ) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, + local_variable_linking_exception=GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=integrations_v2_cfn_resources, + source_resource_tf_config=integrations_v2_config_resources, + destination_resource_tf=lambda_function_tf_resources, + tf_destination_attribute_name="invoke_arn", + terraform_link_field_name="integration_uri", + cfn_link_field_name="IntegrationUri", + terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_gateway_v2_integration_to_lambda_function_callback, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + + @parameterized.expand( + [ + ( + { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": {"IntegrationUri": "invoke_arn"}, + }, + [LogicalIdReference("FunctionA")], + { + "Fn::Join": [ + "", + [ + "arn:", + {"Ref": "AWS::Partition"}, + ":apigateway:", + {"Ref": "AWS::Region"}, + ":lambda:path/2015-03-31/functions/", + {"Fn::GetAtt": ["FunctionA", "Arn"]}, + "/invocations", + ], + ] + }, + ), + ( + { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": {"IntegrationUri": "invoke_arn"}, + }, + [ExistingResourceReference("invoke_arn")], + "invoke_arn", + ), + ] + ) + def test_link_gateway_v2_integration_to_lambda_function_callback( + self, input_gateway_v2_integration, logical_ids, expected_route + ): + gateway_resource = deepcopy(input_gateway_v2_integration) + _link_gateway_v2_integration_to_lambda_function_callback(gateway_resource, logical_ids) + input_gateway_v2_integration["Properties"]["IntegrationUri"] = expected_route + self.assertEqual(gateway_resource, input_gateway_v2_integration) + @parameterized.expand( [ ("integrations/invokeArn", "invokeArn"),