Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Link Gateway V2 APIs to Lambda Functions #5626

Merged
12 changes: 12 additions & 0 deletions samcli/hook_packages/terraform/hooks/prepare/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,18 @@ class GatewayV2RouteToGatewayV2ApiLocalVariablesLinkingLimitationException(Local
"""


class OneGatewayV2ApiToLambdaFunctionLinkingLimitationException(OneResourceLinkingLimitationException):
"""
Exception specific for Gateway V2 API linking to more than one Lambda Function
"""


class GatewayV2ApiToLambdaFunctionLocalVariablesLinkingLimitationException(LocalVariablesLinkingLimitationException):
"""
Exception specific for Gateway V2 API linking to Lambda Function using locals.
"""


class OneGatewayV2StageToGatewayV2ApiLinkingLimitationException(OneResourceLinkingLimitationException):
"""
Exception specific for Gateway V2 Stage linking to more than one Gateway V2 API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException,
GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException,
GatewayResourceToGatewayRestApiLocalVariablesLinkingLimitationException,
GatewayV2ApiToLambdaFunctionLocalVariablesLinkingLimitationException,
GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException,
GatewayV2IntegrationToLambdaFunctionLocalVariablesLinkingLimitationException,
GatewayV2RouteToGatewayV2ApiLocalVariablesLinkingLimitationException,
Expand All @@ -31,6 +32,7 @@
OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException,
OneGatewayResourceToApiGatewayMethodLinkingLimitationException,
OneGatewayResourceToRestApiLinkingLimitationException,
OneGatewayV2ApiToLambdaFunctionLinkingLimitationException,
OneGatewayV2IntegrationToGatewayV2ApiLinkingLimitationException,
OneGatewayV2IntegrationToLambdaFunctionLinkingLimitationException,
OneGatewayV2RouteToGatewayV2ApiLinkingLimitationException,
Expand Down Expand Up @@ -2006,6 +2008,82 @@ def _link_gateway_v2_route_to_api(
ResourceLinker(resource_linking_pair).link_resources()


def _link_gateway_v2_api_to_function_callback(
gateway_v2_api_cfn_resource: Dict, referenced_function_resource_values: List[ReferenceType]
) -> None:
"""
Callback function that is used by the linking algorithm to update an Api Gateway V2 API CFN Resource with
a reference to the Lambda function resource through the AWS_PROXY integration.

Parameters
----------
gateway_v2_api_cfn_resource: Dict
API Gateway V2 API CFN resource
referenced_function_resource_values: List[ReferenceType]
List of referenced Gateway Resources either as the logical id of Lambda function resource
defined in the customer project, or ARN values for actual Lambda function resource defined
in customer's account. This list should always contain one element only.
"""
if len(referenced_function_resource_values) > 1:
raise InvalidResourceLinkingException("Could not link a V2 API to more than one Lambda Function resources")

if not referenced_function_resource_values:
LOG.info("Unable to find any references to Lambda functions, skip linking Lambda function to Gateway V2 API")
return

logical_id = referenced_function_resource_values[0]
gateway_v2_api_cfn_resource["Properties"]["Target"] = (
{"Fn::Sub": INVOKE_ARN_FORMAT.format(function_logical_id=logical_id.value)}
if isinstance(logical_id, LogicalIdReference)
else logical_id.value
)


def _link_gateway_v2_api_to_function(
gateway_api_config_resources: Dict[str, TFResource],
gateway_api_config_address_cfn_resources_map: Dict[str, List],
lambda_function_resources: Dict[str, Dict],
) -> None:
"""
Iterate through all the resources and link the corresponding
Gateway V2 API resources to each Lambda Function

Parameters
----------
gateway_api_config_resources: Dict[str, TFResource]
Dictionary of configuration Gateway APIs
gateway_api_config_address_cfn_resources_map: Dict[str, List]
Dictionary containing resolved configuration addresses matched up to the cfn Gateway API
lambda_function_resources: Dict[str, Dict]
Dictionary of all Terraform Lambda Function resources (not configuration resources).
The dictionary's key is the calculated logical id for each resource.
"""

# Only link APIs to resources if they are "Quick Create" APIs
quick_create_api_config_resources = {
config_address: tf_resource
for config_address, tf_resource in gateway_api_config_resources.items()
if "target" in tf_resource.attributes
}

exceptions = ResourcePairExceptions(
multiple_resource_linking_exception=OneGatewayV2ApiToLambdaFunctionLinkingLimitationException,
local_variable_linking_exception=GatewayV2ApiToLambdaFunctionLocalVariablesLinkingLimitationException,
)
resource_linking_pair = ResourceLinkingPair(
source_resource_cfn_resource=gateway_api_config_address_cfn_resources_map,
source_resource_tf_config=quick_create_api_config_resources,
destination_resource_tf=lambda_function_resources,
tf_destination_attribute_name="invoke_arn",
terraform_link_field_name="target",
cfn_link_field_name="Target",
terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX,
cfn_resource_update_call_back_function=_link_gateway_v2_api_to_function_callback,
linking_exceptions=exceptions,
)
ResourceLinker(resource_linking_pair).link_resources()


def _link_gateway_v2_stage_to_api(
gateway_stage_config_resources: Dict[str, TFResource],
gateway_stage_config_address_cfn_resources_map: Dict[str, List],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
_link_gateway_methods_to_gateway_rest_apis,
_link_gateway_resources_to_gateway_rest_apis,
_link_gateway_stage_to_rest_api,
_link_gateway_v2_api_to_function,
_link_gateway_v2_integration_to_api,
_link_gateway_v2_integration_to_lambda_function,
_link_gateway_v2_route_to_api,
Expand Down Expand Up @@ -119,6 +120,11 @@
dest=TF_AWS_API_GATEWAY_V2_API,
linking_func=_link_gateway_v2_route_to_api,
),
LinkingPairCaller(
source=TF_AWS_API_GATEWAY_V2_API,
dest=TF_AWS_LAMBDA_FUNCTION,
linking_func=_link_gateway_v2_api_to_function,
),
LinkingPairCaller(
source=TF_AWS_API_GATEWAY_V2_STAGE,
dest=TF_AWS_API_GATEWAY_V2_API,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
GatewayV2IntegrationToGatewayV2ApiLocalVariablesLinkingLimitationException,
OneGatewayV2RouteToGatewayV2ApiLinkingLimitationException,
GatewayV2RouteToGatewayV2ApiLocalVariablesLinkingLimitationException,
OneGatewayV2ApiToLambdaFunctionLinkingLimitationException,
GatewayV2ApiToLambdaFunctionLocalVariablesLinkingLimitationException,
OneGatewayV2StageToGatewayV2ApiLinkingLimitationException,
GatewayV2StageToGatewayV2ApiLocalVariablesLinkingLimitationException,
)
Expand Down Expand Up @@ -101,6 +103,8 @@
API_GATEWAY_V2_API_RESOURCE_ADDRESS_PREFIX,
_link_gateway_v2_resource_to_api_callback,
_link_gateway_v2_route_to_api,
_link_gateway_v2_api_to_function,
_link_gateway_v2_api_to_function_callback,
_link_gateway_v2_stage_to_api,
)
from samcli.hook_packages.terraform.hooks.prepare.utilities import get_configuration_address
Expand Down Expand Up @@ -2141,6 +2145,10 @@ def test_link_gateway_integration_to_function_call_back(
_link_gateway_v2_resource_to_api_callback,
"Could not link multiple Gateway V2 Apis to one Gateway V2 resource",
),
(
_link_gateway_v2_api_to_function_callback,
"Could not link a V2 API to more than one Lambda Function resources",
),
]
)
def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message):
Expand All @@ -2158,6 +2166,7 @@ def test_linking_callbacks_raises_multiple_reference_exception(self, linking_cal
(_link_gateway_v2_route_to_integration_callback,),
(_link_gateway_v2_integration_to_lambda_function_callback,),
(_link_gateway_v2_resource_to_api_callback,),
(_link_gateway_v2_api_to_function_callback,),
]
)
def test_linking_callbacks_skips_empty_references(self, linking_call_back_method):
Expand Down Expand Up @@ -2727,3 +2736,72 @@ def test_link_gateway_v2_integration_to_api_callback(
_link_gateway_v2_resource_to_api_callback(gateway_resource, logical_ids)
input_gateway_v2_integration["Properties"]["ApiId"] = expected_api_reference
self.assertEqual(gateway_resource, input_gateway_v2_integration)

@patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_v2_api_to_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_api_to_lambda_function(
self,
mock_resource_linking_exceptions,
mock_resource_linking_pair,
mock_resource_linker,
mock_link_gateway_v2_api_to_function_callback,
):
api_v2_cfn_resources = Mock()
quick_create_resource = TFResource("resource_address", "type", Mock(), {"target": ConstantValue("val")})
combined_resources = {
"ResourceA": quick_create_resource,
"ResourceB": TFResource("resource_address", "type", Mock(), {"name": ConstantValue("MyAPI")}),
}
expected_quick_create_resource = {"ResourceA": quick_create_resource}
lambda_function_tf_resources = Mock()

_link_gateway_v2_api_to_function(combined_resources, api_v2_cfn_resources, lambda_function_tf_resources)

mock_resource_linking_exceptions.assert_called_once_with(
multiple_resource_linking_exception=OneGatewayV2ApiToLambdaFunctionLinkingLimitationException,
local_variable_linking_exception=GatewayV2ApiToLambdaFunctionLocalVariablesLinkingLimitationException,
)

mock_resource_linking_pair.assert_called_once_with(
source_resource_cfn_resource=api_v2_cfn_resources,
source_resource_tf_config=expected_quick_create_resource,
destination_resource_tf=lambda_function_tf_resources,
tf_destination_attribute_name="invoke_arn",
terraform_link_field_name="target",
cfn_link_field_name="Target",
terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX,
cfn_resource_update_call_back_function=mock_link_gateway_v2_api_to_function_callback,
linking_exceptions=mock_resource_linking_exceptions(),
)

mock_resource_linker.assert_called_once_with(mock_resource_linking_pair())

@parameterized.expand(
[
(
{
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {"Target": "functionA.invoke_arn"},
},
[LogicalIdReference("FunctionA")],
{
"Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FunctionA.Arn}/invocations"
},
),
(
{
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {"Target": "functionA.invoke_arn"},
},
[ExistingResourceReference("myapi_arn")],
"myapi_arn",
),
]
)
def test_link_gateway_v2_api_to_function_callback(self, input_gateway_v2_api, logical_ids, expected_api_reference):
gateway_resource = deepcopy(input_gateway_v2_api)
_link_gateway_v2_api_to_function_callback(gateway_resource, logical_ids)
input_gateway_v2_api["Properties"]["Target"] = expected_api_reference
self.assertEqual(gateway_resource, input_gateway_v2_api)
Loading