Skip to content

Commit 57ad2a4

Browse files
committed
fix: use get_function utility
1 parent ddde6c7 commit 57ad2a4

8 files changed

Lines changed: 211 additions & 150 deletions

File tree

samcli/commands/local/lib/local_lambda.py

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,16 @@ def __init__(
9696
self.container_host_interface = container_host_interface
9797
self.extra_hosts = extra_hosts
9898

99-
def get_function(self, function_identifier: str) -> Function:
99+
def get_function(self, function_identifier: str, tenant_id: Optional[str] = None) -> Function:
100100
"""
101101
Get a Lambda function by identifier, raising FunctionNotFound if not found.
102102
103103
Parameters
104104
----------
105105
function_identifier : str
106106
Identifier of the Lambda function, it can be logicalID, function name or full path
107-
107+
tenant_id : Optional[str]
108+
Optional tenant ID for multi-tenant functions
108109
Returns
109110
-------
110111
Function
@@ -116,6 +117,14 @@ def get_function(self, function_identifier: str) -> Function:
116117
When the function identifier doesn't match AWS Lambda's validation pattern
117118
FunctionNotFound
118119
When we cannot find a function with the given identifier
120+
TenantIdValidationError
121+
When the tenant ID is not provided for multi-tenant functions
122+
UnsupportedInlineCodeError
123+
When the function has inline code and is being invoked locally
124+
InvalidIntermediateImageError
125+
When the function has an intermediate image and is being invoked locally
126+
UnsupportedRuntimeArchitectureError
127+
When the function runtime and architecture are not compatible
119128
"""
120129
# Normalize function identifier from ARN if provided
121130
normalized_function_identifier = normalize_sam_function_identifier(function_identifier)
@@ -131,6 +140,36 @@ def get_function(self, function_identifier: str) -> Function:
131140
LOG.info(available_function_message)
132141
raise FunctionNotFound("Unable to find a Function with name '{}'".format(function_identifier))
133142

143+
LOG.debug("Found one Lambda function with name '%s'", function_identifier)
144+
if function.packagetype == ZIP:
145+
if function.inlinecode:
146+
raise UnsupportedInlineCodeError(
147+
"Inline code is not supported for sam local commands."
148+
f" Please write your code in a separate file for the function {function.function_id}."
149+
)
150+
LOG.info("Invoking %s (%s)", function.handler, function.runtime)
151+
elif function.packagetype == IMAGE:
152+
if not function.imageuri:
153+
raise InvalidIntermediateImageError(
154+
f"ImageUri not provided for Function: {function_identifier} of PackageType: {function.packagetype}"
155+
)
156+
LOG.info("Invoking Container created from %s", function.imageuri)
157+
158+
validate_architecture_runtime(function)
159+
160+
# Validate tenant-id for multi-tenant functions
161+
if function.tenancy_config and isinstance(function.tenancy_config, dict):
162+
if not tenant_id:
163+
raise TenantIdValidationError(
164+
"The invoked function is enabled with tenancy configuration. "
165+
"Add a valid tenant ID in your request and try again."
166+
)
167+
elif tenant_id:
168+
raise TenantIdValidationError(
169+
"The invoked function is not enabled with tenancy configuration. "
170+
"Remove the tenant ID from your request and try again."
171+
)
172+
134173
return function
135174

136175
def invoke(
@@ -143,6 +182,7 @@ def invoke(
143182
override_runtime: Optional[str] = None,
144183
invocation_type: str = "RequestResponse",
145184
durable_execution_name: Optional[str] = None,
185+
function: Optional[Function] = None,
146186
) -> Optional[Dict[str, str]]:
147187
"""
148188
Find the Lambda function with given name and invoke it. Pass the given event to the function and return
@@ -176,37 +216,8 @@ def invoke(
176216
When we cannot find a function with the given name
177217
"""
178218
# Get the function configuration
179-
function = self.get_function(function_identifier)
180-
181-
LOG.debug("Found one Lambda function with name '%s'", function_identifier)
182-
if function.packagetype == ZIP:
183-
if function.inlinecode:
184-
raise UnsupportedInlineCodeError(
185-
"Inline code is not supported for sam local commands."
186-
f" Please write your code in a separate file for the function {function.function_id}."
187-
)
188-
LOG.info("Invoking %s (%s)", function.handler, function.runtime)
189-
elif function.packagetype == IMAGE:
190-
if not function.imageuri:
191-
raise InvalidIntermediateImageError(
192-
f"ImageUri not provided for Function: {function_identifier} of PackageType: {function.packagetype}"
193-
)
194-
LOG.info("Invoking Container created from %s", function.imageuri)
195-
196-
validate_architecture_runtime(function)
197-
198-
# Validate tenant-id for multi-tenant functions
199-
if function.tenancy_config and isinstance(function.tenancy_config, dict):
200-
if not tenant_id:
201-
raise TenantIdValidationError(
202-
"The invoked function is enabled with tenancy configuration. "
203-
"Add a valid tenant ID in your request and try again."
204-
)
205-
elif tenant_id:
206-
raise TenantIdValidationError(
207-
"The invoked function is not enabled with tenancy configuration. "
208-
"Remove the tenant ID from your request and try again."
209-
)
219+
if not function:
220+
function = self.get_function(function_identifier, tenant_id)
210221

211222
config = self.get_invoke_config(function, override_runtime)
212223

samcli/local/lambda_service/local_lambda_http_service.py

Lines changed: 26 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
from werkzeug.routing import BaseConverter
1313

1414
from samcli.commands.local.cli_common.durable_context import DurableContext
15-
from samcli.commands.local.lib.exceptions import TenantIdValidationError, UnsupportedInlineCodeError
15+
from samcli.commands.local.lib.exceptions import (
16+
TenantIdValidationError,
17+
UnsupportedInlineCodeError,
18+
InvalidIntermediateImageError,
19+
)
20+
from samcli.lib.providers.provider import Function
1621
from samcli.lib.utils.invocation_type import EVENT
1722
from samcli.lib.utils.name_utils import InvalidFunctionNameException, normalize_sam_function_identifier
1823
from samcli.lib.utils.stream_writer import StreamWriter
@@ -244,42 +249,41 @@ def _invoke_request_handler(self, function_name):
244249
# Extract durable execution name from headers
245250
durable_execution_name = flask_request.headers.get("X-Amz-Durable-Execution-Name")
246251

252+
headers = {"Content-Type": "application/json"}
253+
254+
try:
255+
function = self.lambda_runner.get_function(function_name, tenant_id)
256+
except (InvalidFunctionNameException, TenantIdValidationError, InvalidIntermediateImageError) as e:
257+
LOG.error("Validation error: %s", str(e))
258+
return LambdaErrorResponses.validation_exception(str(e))
259+
except FunctionNotFound:
260+
normalized_function_name = normalize_sam_function_identifier(function_name)
261+
LOG.debug("%s was not found to invoke.", normalized_function_name)
262+
return LambdaErrorResponses.resource_not_found(normalized_function_name)
263+
except UnsupportedInlineCodeError:
264+
return LambdaErrorResponses.not_implemented_locally(
265+
"Inline code is not supported for sam local commands. Please write your code in a separate file."
266+
)
267+
247268
arguments = {
248269
"function_name": function_name,
249270
"request_data": request_data,
250271
"invocation_type": invocation_type,
251272
"durable_execution_name": durable_execution_name,
252273
"tenant_id": tenant_id,
274+
"function": function,
253275
}
254276

255-
headers = {"Content-Type": "application/json"}
256-
257277
if invocation_type == EVENT:
258-
# Validate function exists before submitting async task
259-
if validation_error := self._validate_function_for_invocation(function_name):
260-
return validation_error
261-
262-
# LocalLambdaService is blocking, so threads used by ThreadPoolExecutor
263-
# will be cleaned up when Python exits by the ThreadPoolExecutor implementation
264278
self.executor.submit(self._invoke_async_lambda, **arguments)
265279
return self.service_response("", headers, 202)
266-
267280
try:
268281
invoke_headers, stdout_stream_string, stdout_stream_bytes = self._invoke_lambda(**arguments)
269-
except (InvalidFunctionNameException, TenantIdValidationError) as e:
270-
LOG.error("Validation error: %s", str(e))
271-
return LambdaErrorResponses.validation_exception(str(e))
272282
except UnsupportedInvocationType as e:
273283
LOG.warning(
274-
"invocation-type: %s is not supported. Event and RequestResponse are only supported.", invocation_type
284+
"invocation-type: %s is not supported. Only Event and RequestResponse are supported.", invocation_type
275285
)
276286
return LambdaErrorResponses.not_implemented_locally(str(e))
277-
except FunctionNotFound:
278-
return self._handle_function_not_found(function_name)
279-
except UnsupportedInlineCodeError:
280-
return LambdaErrorResponses.not_implemented_locally(
281-
"Inline code is not supported for sam local commands. Please write your code in a separate file."
282-
)
283287
except DockerContainerCreationFailedException as ex:
284288
return LambdaErrorResponses.container_creation_failed(ex.message)
285289

@@ -296,47 +300,6 @@ def _invoke_request_handler(self, function_name):
296300

297301
return self.service_response(lambda_response, headers, 200)
298302

299-
def _validate_function_for_invocation(self, function_name: str) -> Optional[Response]:
300-
"""
301-
Validates that a function exists and can be invoked.
302-
303-
Parameters
304-
----------
305-
function_name : str
306-
Name or ARN of the function to validate
307-
308-
Returns
309-
-------
310-
Flask.Response or None
311-
Error response if validation fails, None if validation succeeds
312-
"""
313-
try:
314-
self.lambda_runner.get_function(function_name)
315-
return None
316-
except FunctionNotFound:
317-
return self._handle_function_not_found(function_name)
318-
except InvalidFunctionNameException as e:
319-
LOG.error("Validation error: %s", str(e))
320-
return LambdaErrorResponses.validation_exception(str(e))
321-
322-
def _handle_function_not_found(self, function_name: str) -> Response:
323-
"""
324-
Handles FunctionNotFound exception by returning appropriate error response.
325-
326-
Parameters
327-
----------
328-
function_name : str
329-
Name or ARN of the function that was not found
330-
331-
Returns
332-
-------
333-
Flask.Response
334-
Error response for function not found
335-
"""
336-
normalized_function_name = normalize_sam_function_identifier(function_name)
337-
LOG.debug("%s was not found to invoke.", normalized_function_name)
338-
return LambdaErrorResponses.resource_not_found(normalized_function_name)
339-
340303
def _invoke_async_lambda(self, function_name: str, **kwargs) -> None:
341304
"""
342305
Wrapper for _invoke_lambda that runs in an async context (Event invocation type)
@@ -353,6 +316,7 @@ def _invoke_lambda(
353316
invocation_type: str,
354317
durable_execution_name: Optional[str],
355318
tenant_id: Optional[str],
319+
function: Optional[Function],
356320
) -> Tuple[Optional[Dict[str, str]], io.StringIO, io.BytesIO]:
357321
"""
358322
Invokes a Lambda function and returns the result
@@ -370,6 +334,7 @@ def _invoke_lambda(
370334
invocation_type=invocation_type,
371335
durable_execution_name=durable_execution_name,
372336
tenant_id=tenant_id,
337+
function=function,
373338
stdout=stdout_stream_writer,
374339
stderr=self.stderr,
375340
)

samcli/local/lambdafn/runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ def invoke(
309309
if invocation_type not in [EVENT, REQUEST_RESPONSE]:
310310
raise UnsupportedInvocationType(
311311
f"invocation-type: {invocation_type} is not supported. "
312-
"Event and RequestResponse are only supported."
312+
"Only Event and RequestResponse are supported."
313313
)
314314

315315
# The container handles concurrency control internally via its semaphore.

0 commit comments

Comments
 (0)