diff --git a/samcli/commands/local/cli_common/invoke_context.py b/samcli/commands/local/cli_common/invoke_context.py index 9255761143..cc5412431a 100644 --- a/samcli/commands/local/cli_common/invoke_context.py +++ b/samcli/commands/local/cli_common/invoke_context.py @@ -11,10 +11,11 @@ from typing import Any, Dict, List, Optional, TextIO, Tuple, Type, cast from samcli.commands._utils.template import TemplateFailedParsingException, TemplateNotFoundException -from samcli.commands.exceptions import ContainersInitializationException +from samcli.commands.exceptions import ContainersInitializationException, UserException from samcli.commands.local.cli_common.user_exceptions import DebugContextException, InvokeContextException from samcli.commands.local.lib.debug_context import DebugContext from samcli.commands.local.lib.local_lambda import LocalLambdaRunner +from samcli.lib.lint import get_lint_matches from samcli.lib.providers.provider import Function, Stack from samcli.lib.providers.sam_function_provider import RefreshableSamFunctionProvider, SamFunctionProvider from samcli.lib.providers.sam_stack_provider import SamLocalStackProvider @@ -100,6 +101,7 @@ def __init__( container_host_interface: Optional[str] = None, add_host: Optional[dict] = None, invoke_images: Optional[str] = None, + verbose: bool = False, ) -> None: """ Initialize the context @@ -154,6 +156,8 @@ def __init__( Optional. Docker extra hosts support from --add-host parameters invoke_images dict Optional. A dictionary that defines the custom invoke image URI of each function + verbose bool + Set template validation to verbose mode """ self._template_file = template_file self._function_identifier = function_identifier @@ -178,6 +182,7 @@ def __init__( self._aws_region = aws_region self._aws_profile = aws_profile self._shutdown = shutdown + self._verbose = verbose self._container_host = container_host self._container_host_interface = container_host_interface @@ -220,6 +225,13 @@ def __enter__(self) -> "InvokeContext": self._stacks = self._get_stacks() + try: + _, matches_output = get_lint_matches(self._template_file, self._verbose, self._aws_region) + if matches_output: + LOG.warning("Lint Error found, containter creation might fail: %s", matches_output) + except UserException as e: + LOG.warning("Non blocking error found when trying to validate template: %s", e) + _function_providers_class: Dict[ContainersMode, Type[SamFunctionProvider]] = { ContainersMode.WARM: RefreshableSamFunctionProvider, ContainersMode.COLD: SamFunctionProvider, diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index a31d68386d..265d3db16e 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -16,7 +16,11 @@ from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError from samcli.lib.telemetry.metric import track_command from samcli.lib.utils.version_checker import check_newer_version -from samcli.local.docker.exceptions import ContainerNotStartableException, PortAlreadyInUse +from samcli.local.docker.exceptions import ( + ContainerNotStartableException, + DockerContainerCreationFailedException, + PortAlreadyInUse, +) LOG = logging.getLogger(__name__) @@ -200,6 +204,7 @@ def do_cli( # pylint: disable=R0914 container_host_interface=container_host_interface, add_host=add_host, invoke_images=processed_invoke_images, + ctx=ctx, ) as context: # Invoke the function context.local_lambda_runner.invoke( @@ -220,7 +225,7 @@ def do_cli( # pylint: disable=R0914 PortAlreadyInUse, ) as ex: raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex - except DockerImagePullFailedException as ex: + except (DockerImagePullFailedException, DockerContainerCreationFailedException) as ex: raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex except ContainerNotStartableException as ex: raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index fc42468d9d..532678cab6 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -253,6 +253,7 @@ def do_cli( # pylint: disable=R0914 container_host_interface=container_host_interface, invoke_images=processed_invoke_images, add_host=add_host, + ctx=ctx, ) as invoke_context: ssl_context = (ssl_cert_file, ssl_key_file) if ssl_cert_file else None service = LocalApiService( diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 275afa7f1c..22267e23d5 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -205,6 +205,7 @@ def do_cli( # pylint: disable=R0914 container_host_interface=container_host_interface, add_host=add_host, invoke_images=processed_invoke_images, + ctx=ctx, ) as invoke_context: service = LocalLambdaService(lambda_invoke_context=invoke_context, port=port, host=host) service.start() diff --git a/samcli/commands/validate/validate.py b/samcli/commands/validate/validate.py index db0a130b24..732608c7b5 100644 --- a/samcli/commands/validate/validate.py +++ b/samcli/commands/validate/validate.py @@ -142,49 +142,15 @@ def _lint(ctx: Context, template: str) -> None: Path to the template file """ + from samcli.lib.lint import get_lint_matches - import logging - - import cfnlint.core # type: ignore - - from samcli.commands.exceptions import UserException - - cfn_lint_logger = logging.getLogger("cfnlint") - cfn_lint_logger.propagate = False EventTracker.track_event("UsedFeature", "CFNLint") + matches, matches_output = get_lint_matches(template, ctx.debug, ctx.region) + if not matches: + click.secho("{} is a valid SAM Template".format(template), fg="green") + return - try: - lint_args = [template] - if ctx.debug: - lint_args.append("--debug") - if ctx.region: - lint_args.append("--region") - lint_args.append(ctx.region) - - (args, filenames, formatter) = cfnlint.core.get_args_filenames(lint_args) - cfn_lint_logger.setLevel(logging.WARNING) - matches = list(cfnlint.core.get_matches(filenames, args)) - if not matches: - click.secho("{} is a valid SAM Template".format(template), fg="green") - return - - rules = cfnlint.core.get_used_rules() - matches_output = formatter.print_matches(matches, rules, filenames) - - if matches_output: - click.secho(matches_output) - - raise LinterRuleMatchedException( - "Linting failed. At least one linting rule was matched to the provided template." - ) + if matches_output: + click.secho(matches_output) - except cfnlint.core.InvalidRegionException as e: - raise UserException( - "AWS Region was not found. Please configure your region through the --region option", - wrapped_from=e.__class__.__name__, - ) from e - except cfnlint.core.CfnLintExitException as lint_error: - raise UserException( - lint_error, - wrapped_from=lint_error.__class__.__name__, - ) from lint_error + raise LinterRuleMatchedException("Linting failed. At least one linting rule was matched to the provided template.") diff --git a/samcli/lib/lint.py b/samcli/lib/lint.py new file mode 100644 index 0000000000..d0b20945fb --- /dev/null +++ b/samcli/lib/lint.py @@ -0,0 +1,60 @@ +from typing import List, Optional, Tuple + + +def get_lint_matches(template: str, debug: Optional[bool] = None, region: Optional[str] = None) -> Tuple[List, str]: + """ + Parses provided SAM template and maps errors from CloudFormation template back to SAM template. + + Cfn-lint loggers are added to the SAM cli logging hierarchy which at the root logger + configures with INFO level logging and a different formatting. This exposes and duplicates + some cfn-lint logs that are not typically shown to customers. Explicitly setting the level to + WARNING and propagate to be False remediates these issues. + + Parameters + ----------- + template + Path to the template file + debug + Enable debug mode + region + AWS region to run against + + """ + + import logging + + import cfnlint.core # type: ignore + + from samcli.commands.exceptions import UserException + + cfn_lint_logger = logging.getLogger("cfnlint") + cfn_lint_logger.propagate = False + + try: + lint_args = [template] + if debug: + lint_args.append("--debug") + if region: + lint_args.append("--region") + lint_args.append(region) + + (args, filenames, formatter) = cfnlint.core.get_args_filenames(lint_args) + cfn_lint_logger.setLevel(logging.WARNING) + matches = list(cfnlint.core.get_matches(filenames, args)) + if not matches: + return matches, "" + + rules = cfnlint.core.get_used_rules() + matches_output = formatter.print_matches(matches, rules, filenames) + return matches, matches_output + + except cfnlint.core.InvalidRegionException as e: + raise UserException( + "AWS Region was not found. Please configure your region through the --region option", + wrapped_from=e.__class__.__name__, + ) from e + except cfnlint.core.CfnLintExitException as lint_error: + raise UserException( + lint_error, + wrapped_from=lint_error.__class__.__name__, + ) from lint_error diff --git a/samcli/local/apigw/local_apigw_service.py b/samcli/local/apigw/local_apigw_service.py index 1e0f871fcd..78104c0f7b 100644 --- a/samcli/local/apigw/local_apigw_service.py +++ b/samcli/local/apigw/local_apigw_service.py @@ -30,6 +30,7 @@ from samcli.local.apigw.path_converter import PathConverter from samcli.local.apigw.route import Route from samcli.local.apigw.service_error_responses import ServiceErrorResponses +from samcli.local.docker.exceptions import DockerContainerCreationFailedException from samcli.local.events.api_event import ( ContextHTTP, ContextIdentity, @@ -730,6 +731,8 @@ def _request_handler(self, **kwargs): ) except LambdaResponseParseException: endpoint_service_error = ServiceErrorResponses.lambda_body_failure_response() + except DockerContainerCreationFailedException as ex: + endpoint_service_error = ServiceErrorResponses.container_creation_failed(ex.message) if endpoint_service_error: return endpoint_service_error diff --git a/samcli/local/apigw/service_error_responses.py b/samcli/local/apigw/service_error_responses.py index 42c91e22c1..6c19b3a2e7 100644 --- a/samcli/local/apigw/service_error_responses.py +++ b/samcli/local/apigw/service_error_responses.py @@ -101,3 +101,13 @@ def route_not_found(*args): """ response_data = jsonify(ServiceErrorResponses._MISSING_AUTHENTICATION) return make_response(response_data, ServiceErrorResponses.HTTP_STATUS_CODE_403) + + @staticmethod + def container_creation_failed(message): + """ + Constuct a Flask Response for when container creation fails for a Lambda Function + + :return: a Flask Response + """ + response_data = jsonify({"message": message}) + return make_response(response_data, ServiceErrorResponses.HTTP_STATUS_CODE_501) diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index 9e83e8eca0..a34e96c7c1 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -17,14 +17,23 @@ import docker import requests -from docker.errors import NotFound as DockerNetworkNotFound +from docker.errors import ( + APIError as DockerAPIError, +) +from docker.errors import ( + NotFound as DockerNetworkNotFound, +) from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.utils.retry import retry from samcli.lib.utils.stream_writer import StreamWriter from samcli.lib.utils.tar import extract_tarfile from samcli.local.docker.effective_user import ROOT_USER_ID, EffectiveUser -from samcli.local.docker.exceptions import ContainerNotStartableException, PortAlreadyInUse +from samcli.local.docker.exceptions import ( + ContainerNotStartableException, + DockerContainerCreationFailedException, + PortAlreadyInUse, +) from samcli.local.docker.utils import NoFreePortsError, find_free_port, to_posix_path LOG = logging.getLogger(__name__) @@ -227,7 +236,12 @@ def create(self): if self._extra_hosts: kwargs["extra_hosts"] = self._extra_hosts - real_container = self.docker_client.containers.create(self._image, **kwargs) + try: + real_container = self.docker_client.containers.create(self._image, **kwargs) + except DockerAPIError as ex: + raise DockerContainerCreationFailedException( + f"Container creation failed: {ex.explanation}, check template for potential issue" + ) self.id = real_container.id self._logs_thread = None diff --git a/samcli/local/docker/exceptions.py b/samcli/local/docker/exceptions.py index 6ff7ad3850..e22eac1583 100644 --- a/samcli/local/docker/exceptions.py +++ b/samcli/local/docker/exceptions.py @@ -37,3 +37,9 @@ class InvalidRuntimeException(UserException): """ Raised when an invalid runtime is specified for a Lambda Function """ + + +class DockerContainerCreationFailedException(UserException): + """ + Docker Container Creation failed. It could be due to invalid template + """ diff --git a/samcli/local/lambda_service/lambda_error_responses.py b/samcli/local/lambda_service/lambda_error_responses.py index 89ef7b13dc..3596828bf5 100644 --- a/samcli/local/lambda_service/lambda_error_responses.py +++ b/samcli/local/lambda_service/lambda_error_responses.py @@ -20,6 +20,7 @@ class LambdaErrorResponses: InvalidRequestContentException = ("InvalidRequestContent", 400) NotImplementedException = ("NotImplemented", 501) + ContainerCreationFailed = ("ContainerCreationFailed", 501) PathNotFoundException = ("PathNotFoundLocally", 404) @@ -204,6 +205,31 @@ def generic_method_not_allowed(*args): exception_tuple[1], ) + @staticmethod + def container_creation_failed(message): + """ + Creates a Container Creation Failed response + + Parameters + ---------- + args list + List of arguments Flask passes to the method + + Returns + ------- + Flask.Response + A response object representing the ContainerCreationFailed Error + """ + error_type, status_code = LambdaErrorResponses.ContainerCreationFailed + return BaseLocalService.service_response( + LambdaErrorResponses._construct_error_response_body( + LambdaErrorResponses.LOCAL_SERVICE_ERROR, + message, + ), + LambdaErrorResponses._construct_headers(error_type), + status_code, + ) + @staticmethod def _construct_error_response_body(error_type, error_message): """ diff --git a/samcli/local/lambda_service/local_lambda_invoke_service.py b/samcli/local/lambda_service/local_lambda_invoke_service.py index a847802b3c..51030f6806 100644 --- a/samcli/local/lambda_service/local_lambda_invoke_service.py +++ b/samcli/local/lambda_service/local_lambda_invoke_service.py @@ -9,6 +9,7 @@ from samcli.commands.local.lib.exceptions import UnsupportedInlineCodeError from samcli.lib.utils.stream_writer import StreamWriter +from samcli.local.docker.exceptions import DockerContainerCreationFailedException from samcli.local.lambdafn.exceptions import FunctionNotFound from samcli.local.services.base_local_service import BaseLocalService, LambdaOutputParser @@ -178,6 +179,8 @@ def _invoke_request_handler(self, function_name): return LambdaErrorResponses.not_implemented_locally( "Inline code is not supported for sam local commands. Please write your code in a separate file." ) + except DockerContainerCreationFailedException as ex: + return LambdaErrorResponses.container_creation_failed(ex.message) lambda_response, is_lambda_user_error_response = LambdaOutputParser.get_lambda_output( stdout_stream_string, stdout_stream_bytes diff --git a/samcli/local/lambdafn/runtime.py b/samcli/local/lambdafn/runtime.py index 83af384dcf..38922f858d 100644 --- a/samcli/local/lambdafn/runtime.py +++ b/samcli/local/lambdafn/runtime.py @@ -16,7 +16,7 @@ from samcli.lib.utils.packagetype import ZIP from samcli.local.docker.container import Container from samcli.local.docker.container_analyzer import ContainerAnalyzer -from samcli.local.docker.exceptions import ContainerFailureError +from samcli.local.docker.exceptions import ContainerFailureError, DockerContainerCreationFailedException from samcli.local.docker.lambda_container import LambdaContainer from ...lib.providers.provider import LayerVersion @@ -112,6 +112,10 @@ def create( self._container_manager.create(container) return container + except DockerContainerCreationFailedException: + LOG.warning("Failed to create container for function %s", function_config.full_path) + raise + except KeyboardInterrupt: LOG.debug("Ctrl+C was pressed. Aborting container creation") raise diff --git a/tests/unit/commands/local/invoke/test_cli.py b/tests/unit/commands/local/invoke/test_cli.py index a033905b8c..476dd99ce2 100644 --- a/tests/unit/commands/local/invoke/test_cli.py +++ b/tests/unit/commands/local/invoke/test_cli.py @@ -6,7 +6,11 @@ from unittest.mock import patch, Mock from parameterized import parameterized, param -from samcli.local.docker.exceptions import ContainerNotStartableException, PortAlreadyInUse +from samcli.local.docker.exceptions import ( + ContainerNotStartableException, + PortAlreadyInUse, + DockerContainerCreationFailedException, +) from samcli.local.lambdafn.exceptions import FunctionNotFound from samcli.lib.providers.exceptions import InvalidLayerReference from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException @@ -112,6 +116,7 @@ def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMo container_host_interface=self.container_host_interface, add_host=self.add_host, invoke_images={None: "amazon/aws-sam-cli-emulation-image-python3.9"}, + ctx=self.ctx_mock, ) context_mock.local_lambda_runner.invoke.assert_called_with( @@ -152,6 +157,7 @@ def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock): container_host_interface=self.container_host_interface, add_host=self.add_host, invoke_images={None: "amazon/aws-sam-cli-emulation-image-python3.9"}, + ctx=self.ctx_mock, ) get_event_mock.assert_not_called() @@ -263,6 +269,10 @@ def test_must_raise_user_exception_on_invalid_env_vars(self, get_event_mock, Inv PortAlreadyInUse("Container cannot be started, provided port already in use"), "Container cannot be started, provided port already in use", ), + param( + DockerContainerCreationFailedException("Container creation failed, check template for potential issue"), + "Container creation failed, check template for potential issue", + ), ] ) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") diff --git a/tests/unit/commands/local/start_api/test_cli.py b/tests/unit/commands/local/start_api/test_cli.py index 1860dd66c0..54c7e663d1 100644 --- a/tests/unit/commands/local/start_api/test_cli.py +++ b/tests/unit/commands/local/start_api/test_cli.py @@ -97,6 +97,7 @@ def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, container_host_interface=self.container_host_interface, add_host=self.add_host, invoke_images={}, + ctx=self.ctx_mock, ) local_api_service_mock.assert_called_with( diff --git a/tests/unit/commands/local/start_lambda/test_cli.py b/tests/unit/commands/local/start_lambda/test_cli.py index aa779ed871..d743f13e3e 100644 --- a/tests/unit/commands/local/start_lambda/test_cli.py +++ b/tests/unit/commands/local/start_lambda/test_cli.py @@ -84,6 +84,7 @@ def test_cli_must_setup_context_and_start_service(self, local_lambda_service_moc container_host_interface=self.container_host_interface, add_host=self.add_host, invoke_images={}, + ctx=self.ctx_mock, ) local_lambda_service_mock.assert_called_with(lambda_invoke_context=context_mock, port=self.port, host=self.host) diff --git a/tests/unit/lib/test_lint.py b/tests/unit/lib/test_lint.py new file mode 100644 index 0000000000..d5eda1f8aa --- /dev/null +++ b/tests/unit/lib/test_lint.py @@ -0,0 +1,50 @@ +from unittest import TestCase +from unittest.mock import Mock, patch + +from parameterized import parameterized + +from samcli.lib.lint import get_lint_matches + + +class TestLint(TestCase): + args_mock = Mock() + filenames_mock = Mock() + formatter_mock = Mock() + get_args_filenames_mock = Mock() + get_matches_mock = Mock() + + @parameterized.expand( + [ + (("path/to/template", None, None), ["path/to/template"]), + (("path/to/template", True, None), ["path/to/template", "--debug"]), + (("path/to/template", False, "us-east-1"), ["path/to/template", "--region", "us-east-1"]), + ] + ) + @patch("cfnlint.core") + def test_empty_matches(self, call_args, expect_called_args, cfnlint_mock): + self.get_args_filenames_mock.return_value = (self.args_mock, self.filenames_mock, self.formatter_mock) + cfnlint_mock.get_args_filenames = self.get_args_filenames_mock + self.get_matches_mock.return_value = [] + cfnlint_mock.get_matches = self.get_matches_mock + actual = get_lint_matches(*call_args) + self.get_args_filenames_mock.assert_called_with(expect_called_args) + self.get_matches_mock.assert_called_with(self.filenames_mock, self.args_mock) + self.assertEqual(actual, ([], "")) + + @patch("cfnlint.core") + def test_non_empty_matches(self, cfnlint_mock): + self.get_args_filenames_mock.return_value = (self.args_mock, self.filenames_mock, self.formatter_mock) + cfnlint_mock.get_args_filenames = self.get_args_filenames_mock + match = Mock() + self.get_matches_mock.return_value = [match] + cfnlint_mock.get_matches = self.get_matches_mock + cfnlint_mock.get_used_rules = Mock() + self.formatter_mock.print_matches = Mock() + self.formatter_mock.print_matches.return_value = "lint error" + + actual = get_lint_matches("path_to_template") + self.get_args_filenames_mock.assert_called_with(["path_to_template"]) + self.get_matches_mock.assert_called_with(self.filenames_mock, self.args_mock) + cfnlint_mock.get_used_rules.assert_called_once() + self.formatter_mock.print_matches.assert_called_once() + self.assertEqual(actual, ([match], "lint error")) diff --git a/tests/unit/local/apigw/test_local_apigw_service.py b/tests/unit/local/apigw/test_local_apigw_service.py index c3442aaf06..54bf0da1e0 100644 --- a/tests/unit/local/apigw/test_local_apigw_service.py +++ b/tests/unit/local/apigw/test_local_apigw_service.py @@ -23,6 +23,7 @@ LambdaResponseParseException, PayloadFormatVersionValidateException, ) +from samcli.local.docker.exceptions import DockerContainerCreationFailedException from samcli.local.lambdafn.exceptions import FunctionNotFound from samcli.commands.local.lib.exceptions import UnsupportedInlineCodeError @@ -457,6 +458,27 @@ def test_request_handles_error_when_invoke_function_with_inline_code( self.assertEqual(response, not_implemented_response_mock) + @patch.object(LocalApigwService, "get_request_methods_endpoints") + @patch("samcli.local.apigw.local_apigw_service.ServiceErrorResponses") + @patch("samcli.local.apigw.local_apigw_service.LocalApigwService._generate_lambda_event") + def test_request_handles_error_when_container_creation_failed( + self, generate_mock, service_error_responses_patch, request_mock + ): + generate_mock.return_value = {} + container_creation_failed_response_mock = Mock() + self.api_service._get_current_route = MagicMock() + self.api_service._get_current_route.return_value.payload_format_version = "2.0" + self.api_service._get_current_route.return_value.authorizer_object = None + self.api_service._get_current_route.methods = [] + + service_error_responses_patch.container_creation_failed.return_value = container_creation_failed_response_mock + + self.lambda_runner.invoke.side_effect = DockerContainerCreationFailedException("container creation failed") + request_mock.return_value = ("test", "test") + response = self.api_service._request_handler() + + self.assertEqual(response, container_creation_failed_response_mock) + @patch.object(LocalApigwService, "get_request_methods_endpoints") def test_request_throws_when_invoke_fails(self, request_mock): self.lambda_runner.invoke.side_effect = Exception() diff --git a/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py b/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py index e338762db9..af5a2a5aa2 100644 --- a/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py +++ b/tests/unit/local/lambda_service/test_local_lambda_invoke_service.py @@ -1,6 +1,7 @@ from unittest import TestCase from unittest.mock import Mock, patch, ANY, call +from samcli.local.docker.exceptions import DockerContainerCreationFailedException from samcli.local.lambda_service import local_lambda_invoke_service from samcli.local.lambda_service.local_lambda_invoke_service import LocalLambdaInvokeService, FunctionNamePathConverter from samcli.local.lambdafn.exceptions import FunctionNotFound @@ -111,6 +112,29 @@ def test_invoke_request_function_contains_inline_code(self, lambda_error_respons lambda_error_responses_mock.not_implemented_locally.assert_called() + @patch("samcli.local.lambda_service.local_lambda_invoke_service.LambdaErrorResponses") + def test_invoke_request_container_creation_failed(self, lambda_error_responses_mock): + request_mock = Mock() + request_mock.get_data.return_value = b"{}" + local_lambda_invoke_service.request = request_mock + + lambda_runner_mock = Mock() + lambda_runner_mock.invoke.side_effect = DockerContainerCreationFailedException("container creation failed") + + lambda_error_responses_mock.container_creation_failed.return_value = "Container creation failed" + + service = LocalLambdaInvokeService(lambda_runner=lambda_runner_mock, port=3000, host="localhost") + + response = service._invoke_request_handler(function_name="FunctionContainerCreationFailed") + + self.assertEqual(response, "Container creation failed") + + lambda_runner_mock.invoke.assert_called_once_with( + "FunctionContainerCreationFailed", "{}", stdout=ANY, stderr=None + ) + + lambda_error_responses_mock.container_creation_failed.assert_called() + @patch("samcli.local.lambda_service.local_lambda_invoke_service.LocalLambdaInvokeService.service_response") @patch("samcli.local.lambda_service.local_lambda_invoke_service.LambdaOutputParser") def test_request_handler_returns_process_stdout_when_making_response(