diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index 046d316453..491cdd9933 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -261,6 +261,20 @@ class GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationExcepti """ +class OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway V2 Integration linking to more than one Gateway V2 API + """ + + +class GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException( + LocalVariablesLinkingLimitationException +): + """ + Exception specific for Gateway V2 Integration linking to Gateway V2 API 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 948e0e1201..09d03a5b01 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, + GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException, GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, GatewayV2RouteToGatewayV2IntegrationLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, @@ -28,6 +29,7 @@ OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, OneGatewayResourceToRestApiLinkingLimitationException, + OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException, OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, OneGatewayV2RouteToGatewayV2IntegrationLinkingLimitationException, OneLambdaFunctionResourceToApiGatewayIntegrationLinkingLimitationException, @@ -60,6 +62,7 @@ API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_resource." API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_authorizer." API_GATEWAY_V2_INTEGRATION_RESOURCE_ADDRESS_PREFIX = "aws_apigatewayv2_integration" +API_GATEWAY_V2_API_RESOURCE_ADDRESS_PREFIX = "aws_apigatewayv2_api" TERRAFORM_LOCAL_VARIABLES_ADDRESS_PREFIX = "local." DATA_RESOURCE_ADDRESS_PREFIX = "data." @@ -1892,3 +1895,71 @@ def _link_gateway_v2_integration_to_lambda_function( linking_exceptions=exceptions, ) ResourceLinker(resource_linking_pair).link_resources() + + +def _link_gateway_v2_integration_to_api_callback( + gateway_v2_integration_cfn_resource: Dict, gateway_v2_api_resources: List[ReferenceType] +): + """ + Callback function that is used by the linking algorithm to update a CFN V2 Integration Resource with + a reference to the Gateway V2 Api resource + + Parameters + ---------- + gateway_v2_integration_cfn_resource: Dict + API Gateway V2 Integration CFN resource + gateway_v2_api_resources: List[ReferenceType] + List of referenced Gateway V2 Apis either as the logical id of Apis resources + defined in the customer project, or ARN values for actual Api defined + in customer's account. This list should always contain one element only. + """ + if len(gateway_v2_api_resources) > 1: + raise InvalidResourceLinkingException("Could not link multiple Gateway V2 Apis to one Gateway V2 Integration") + + if not gateway_v2_api_resources: + LOG.info( + "Unable to find any references to Gateway V2 APIs, skip linking Gateway V2 Integration to Gateway V2 APIs" + ) + return + + logical_id = gateway_v2_api_resources[0] + gateway_v2_integration_cfn_resource["Properties"]["ApiId"] = ( + {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value + ) + + +def _link_gateway_v2_integration_to_api( + gateway_integration_config_resources: Dict[str, TFResource], + gateway_integration_config_address_cfn_resources_map: Dict[str, List], + api_resources: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding + Gateway V2 Integration resources to each Gateway V2 Api + + Parameters + ---------- + gateway_integration_config_resources: Dict[str, TFResource] + Dictionary of configuration Gateway Integrations + gateway_integration_config_address_cfn_resources_map: Dict[str, List] + Dictionary containing resolved configuration addresses matched up to the cfn Gateway Integration + api_resources: Dict[str, Dict] + Dictionary of all Terraform Gateway V2 Api resources (not configuration resources). + The dictionary's key is the calculated logical id for each resource. + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException, + local_variable_linking_exception=GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=gateway_integration_config_address_cfn_resources_map, + source_resource_tf_config=gateway_integration_config_resources, + destination_resource_tf=api_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="api_id", + cfn_link_field_name="ApiId", + terraform_resource_type_prefix=API_GATEWAY_V2_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_v2_integration_to_api_callback, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py index df5753084c..50bb29fecc 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py @@ -126,6 +126,15 @@ def __init__(self): super(ApiGatewayV2RouteProperties, self).__init__() +class ApiGatewayV2ApiProperties(ResourceProperties): + """ + Contains the collection logic of the required properties for linking the aws_apigatewayv2_api resources. + """ + + def __init__(self): + super(ApiGatewayV2ApiProperties, self).__init__() + + class ApiGatewayV2IntegrationProperties(ResourceProperties): """ Contains the collection logic of the required properties for linking the aws_api_gateway_v2_authorizer 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 6b8dd95257..b0ded92e5f 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -8,6 +8,7 @@ TF_AWS_API_GATEWAY_RESOURCE, TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_STAGE, + TF_AWS_API_GATEWAY_V2_API, TF_AWS_API_GATEWAY_V2_INTEGRATION, TF_AWS_API_GATEWAY_V2_ROUTE, TF_AWS_LAMBDA_FUNCTION, @@ -26,6 +27,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_api, _link_gateway_v2_integration_to_lambda_function, _link_gateway_v2_route_to_integration, _link_lambda_functions_to_layers, @@ -104,4 +106,9 @@ dest=TF_AWS_LAMBDA_FUNCTION, linking_func=_link_gateway_v2_integration_to_lambda_function, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_V2_INTEGRATION, + dest=TF_AWS_API_GATEWAY_V2_API, + linking_func=_link_gateway_v2_integration_to_api, + ), ] diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py index a4ada05ebe..ce15cbf048 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py @@ -9,6 +9,7 @@ TF_AWS_API_GATEWAY_RESOURCE, TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_STAGE, + TF_AWS_API_GATEWAY_V2_API, TF_AWS_API_GATEWAY_V2_INTEGRATION, TF_AWS_API_GATEWAY_V2_ROUTE, TF_AWS_LAMBDA_FUNCTION, @@ -20,6 +21,7 @@ ApiGatewayResourceProperties, ApiGatewayRestApiProperties, ApiGatewayStageProperties, + ApiGatewayV2ApiProperties, ApiGatewayV2IntegrationProperties, ApiGatewayV2RouteProperties, ) @@ -55,4 +57,5 @@ def get_resource_property_mapping() -> Dict[str, ResourceProperties]: TF_AWS_API_GATEWAY_AUTHORIZER: ApiGatewayAuthorizerProperties(), TF_AWS_API_GATEWAY_V2_ROUTE: ApiGatewayV2RouteProperties(), TF_AWS_API_GATEWAY_V2_INTEGRATION: ApiGatewayV2IntegrationProperties(), + TF_AWS_API_GATEWAY_V2_API: ApiGatewayV2ApiProperties(), } 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 474013fccc..a4ced114eb 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 @@ -41,6 +41,8 @@ GatewayV2RouteToGatewayV2IntegrationLocalVariablesLinkingLimitationException, OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException, GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException, + OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException, + GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException, ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( @@ -91,6 +93,9 @@ _link_gateway_v2_integration_to_lambda_function_callback, _link_gateway_v2_integration_to_lambda_function, _extract_gateway_v2_integration_id_from_route_target_value, + _link_gateway_v2_integration_to_api, + API_GATEWAY_V2_API_RESOURCE_ADDRESS_PREFIX, + _link_gateway_v2_integration_to_api_callback, ) from samcli.hook_packages.terraform.hooks.prepare.utilities import get_configuration_address from samcli.hook_packages.terraform.hooks.prepare.types import ( @@ -2126,6 +2131,10 @@ def test_link_gateway_integration_to_function_call_back( _link_gateway_v2_integration_to_lambda_function_callback, "Could not link multiple lambda functions to one Gateway V2 Integration", ), + ( + _link_gateway_v2_integration_to_api_callback, + "Could not link multiple Gateway V2 Apis to one Gateway V2 Integration", + ), ] ) def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message): @@ -2142,6 +2151,7 @@ def test_linking_callbacks_raises_multiple_reference_exception(self, linking_cal (_link_gateway_method_to_gateway_authorizer_call_back,), (_link_gateway_v2_route_to_integration_callback,), (_link_gateway_v2_integration_to_lambda_function_callback,), + (_link_gateway_v2_integration_to_api_callback,), ] ) def test_linking_callbacks_skips_empty_references(self, linking_call_back_method): @@ -2573,3 +2583,69 @@ def test_link_gateway_v2_integration_to_lambda_function_callback( def test_extract_gateway_v2_integration_id_from_route_target_value(self, input_value, expected_output): output = _extract_gateway_v2_integration_id_from_route_target_value(input_value) self.assertEqual(output, expected_output) + + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_v2_integration_to_api_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_gateway_v2_api( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_gateway_v2_integration_to_api_callback, + ): + integrations_v2_cfn_resources = Mock() + integrations_v2_config_resources = Mock() + apis_v2_tf_resources = Mock() + + _link_gateway_v2_integration_to_api( + integrations_v2_config_resources, integrations_v2_cfn_resources, apis_v2_tf_resources + ) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException, + local_variable_linking_exception=GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException, + ) + + 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=apis_v2_tf_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="api_id", + cfn_link_field_name="ApiId", + terraform_resource_type_prefix=API_GATEWAY_V2_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_gateway_v2_integration_to_api_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": {"ApiId": "api_id"}, + }, + [LogicalIdReference("myapi")], + {"Ref": "myapi"}, + ), + ( + { + "Type": "AWS::ApiGatewayV2::Integration", + "Properties": {"ApiId": "api_id"}, + }, + [ExistingResourceReference("myapi_arn")], + "myapi_arn", + ), + ] + ) + def test_link_gateway_v2_integration_to_api_callback( + self, input_gateway_v2_integration, logical_ids, expected_api_reference + ): + gateway_resource = deepcopy(input_gateway_v2_integration) + _link_gateway_v2_integration_to_api_callback(gateway_resource, logical_ids) + input_gateway_v2_integration["Properties"]["ApiId"] = expected_api_reference + self.assertEqual(gateway_resource, input_gateway_v2_integration)