def test_must_raise_user_exception_on_invalid_imageuri(self, invoke_context_mock): invoke_context_mock.side_effect = InvalidIntermediateImageError("invalid imageuri") with self.assertRaises(UserException) as context: self.call_cli() msg = str(context.exception) expected = "invalid imageuri" self.assertEqual(msg, expected)
def build(self, runtime, packagetype, image, layers, stream=None): """ Build the image if one is not already on the system that matches the runtime and layers Parameters ---------- runtime str Name of the Lambda runtime packagetype str Packagetype for the Lambda image str Pre-defined invocation image. layers list(samcli.commands.local.lib.provider.Layer) List of layers Returns ------- str The image to be used (REPOSITORY:TAG) """ image_name = None if packagetype == IMAGE: image_name = image elif packagetype == ZIP: image_name = f"{self._INVOKE_REPO_PREFIX}-{runtime}:latest" if not image_name: raise InvalidIntermediateImageError( f"Invalid PackageType, PackageType needs to be one of [{ZIP}, {IMAGE}]" ) if image: self.skip_pull_image = True # Default image tag to be the base image with a tag of 'rapid' instead of latest. # If the image name had a digest, removing the @ so that a valid image name can be constructed # to use for the local invoke image name. image_tag = f"{image_name.split(':')[0].replace('@', '')}:rapid-{version}" downloaded_layers = [] if layers and packagetype == ZIP: downloaded_layers = self.layer_downloader.download_all( layers, self.force_image_build) docker_image_version = self._generate_docker_image_version( downloaded_layers, runtime) image_tag = f"{self._SAM_CLI_REPO_NAME}:{docker_image_version}" image_not_found = False # If we are not using layers, build anyways to ensure any updates to rapid get added try: self.docker_client.images.get(image_tag) except docker.errors.ImageNotFound: LOG.info("Image was not found.") image_not_found = True if (self.force_image_build or image_not_found or any(layer.is_defined_within_template for layer in downloaded_layers) or not runtime): stream_writer = stream or StreamWriter(sys.stderr) stream_writer.write("Building image...") stream_writer.flush() self._build_image(image if image else image_name, image_tag, downloaded_layers, stream=stream_writer) return image_tag
class TestCli(TestCase): def setUp(self): self.function_id = "id" self.template = "template" self.eventfile = "eventfile" self.env_vars = "env-vars" self.container_env_vars = "debug-env-vars" self.debug_ports = [123] self.debug_args = "args" self.debugger_path = "/test/path" self.docker_volume_basedir = "basedir" self.docker_network = "network" self.log_file = "logfile" self.skip_pull_image = True self.no_event = True self.parameter_overrides = {} self.layer_cache_basedir = "/some/layers/path" self.force_image_build = True self.shutdown = False self.region_name = "region" self.profile = "profile" @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_cli_must_setup_context_and_invoke(self, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() InvokeContextMock.return_value.__enter__.return_value = context_mock invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) InvokeContextMock.assert_called_with( template_file=self.template, function_identifier=self.function_id, env_vars_file=self.env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars_file=self.container_env_vars, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, aws_region=self.region_name, aws_profile=self.profile, ) context_mock.local_lambda_runner.invoke.assert_called_with( context_mock.function_identifier, event=event_data, stdout=context_mock.stdout, stderr=context_mock.stderr) get_event_mock.assert_called_with(self.eventfile) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_cli_must_invoke_with_no_event(self, get_event_mock, InvokeContextMock): self.event = None ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() InvokeContextMock.return_value.__enter__.return_value = context_mock invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.event, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) InvokeContextMock.assert_called_with( template_file=self.template, function_identifier=self.function_id, env_vars_file=self.env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, debug_ports=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars_file=self.container_env_vars, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, aws_region=self.region_name, aws_profile=self.profile, ) get_event_mock.assert_not_called() context_mock.local_lambda_runner.invoke.assert_called_with( context_mock.function_identifier, event="{}", stdout=context_mock.stdout, stderr=context_mock.stderr) @parameterized.expand([ param(FunctionNotFound("not found"), "Function id not found in template"), param(DockerImagePullFailedException("Failed to pull image"), "Failed to pull image"), ]) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_function_not_found( self, side_effect_exception, expected_exectpion_message, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() InvokeContextMock.return_value.__enter__.return_value = context_mock context_mock.local_lambda_runner.invoke.side_effect = side_effect_exception with self.assertRaises(UserException) as ex_ctx: invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) msg = str(ex_ctx.exception) self.assertEqual(msg, expected_exectpion_message) @parameterized.expand([ param( InvalidIntermediateImageError( "ImageUri not set to a reference-able image for Function: MyFunction" ), "ImageUri not set to a reference-able image for Function: MyFunction", ), ]) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_function_local_invoke_image_not_found_for_IMAGE_packagetype( self, side_effect_exception, expected_exectpion_message, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() InvokeContextMock.return_value.__enter__.return_value = context_mock context_mock.local_lambda_runner.invoke.side_effect = side_effect_exception with self.assertRaises(UserException) as ex_ctx: invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) msg = str(ex_ctx.exception) self.assertEqual(msg, expected_exectpion_message) @parameterized.expand([ (InvalidSamDocumentException("bad template"), "bad template"), ( InvalidLayerReference(), "Layer References need to be of type " "'AWS::Serverless::LayerVersion' or 'AWS::Lambda::LayerVersion'", ), (DebuggingNotSupported("Debugging not supported"), "Debugging not supported"), ]) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_invalid_sam_template( self, exeception_to_raise, execption_message, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile InvokeContextMock.side_effect = exeception_to_raise with self.assertRaises(UserException) as ex_ctx: invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) msg = str(ex_ctx.exception) self.assertEqual(msg, execption_message) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_invalid_env_vars( self, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile InvokeContextMock.side_effect = OverridesNotWellDefinedError( "bad env vars") with self.assertRaises(UserException) as ex_ctx: invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) msg = str(ex_ctx.exception) self.assertEqual(msg, "bad env vars") @parameterized.expand([ param( ContainerNotStartableException( "Container cannot be started, no free ports on host"), "Container cannot be started, no free ports on host", ), ]) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.invoke.cli._get_event") def test_must_raise_user_exception_on_function_no_free_ports( self, side_effect_exception, expected_exectpion_message, get_event_mock, InvokeContextMock): event_data = "data" get_event_mock.return_value = event_data ctx_mock = Mock() ctx_mock.region = self.region_name ctx_mock.profile = self.profile # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() InvokeContextMock.return_value.__enter__.return_value = context_mock context_mock.local_lambda_runner.invoke.side_effect = side_effect_exception with self.assertRaises(UserException) as ex_ctx: invoke_cli( ctx=ctx_mock, function_identifier=self.function_id, template=self.template, event=self.eventfile, no_event=self.no_event, env_vars=self.env_vars, debug_port=self.debug_ports, debug_args=self.debug_args, debugger_path=self.debugger_path, container_env_vars=self.container_env_vars, docker_volume_basedir=self.docker_volume_basedir, docker_network=self.docker_network, log_file=self.log_file, skip_pull_image=self.skip_pull_image, parameter_overrides=self.parameter_overrides, layer_cache_basedir=self.layer_cache_basedir, force_image_build=self.force_image_build, shutdown=self.shutdown, ) msg = str(ex_ctx.exception) self.assertEqual(msg, expected_exectpion_message)
def invoke(self, function_name, event, stdout=None, stderr=None): """ Find the Lambda function with given name and invoke it. Pass the given event to the function and return response through the given streams. This function will block until either the function completes or times out. Parameters ---------- function_name str Name of the Lambda function to invoke event str Event data passed to the function. Must be a valid JSON String. stdout samcli.lib.utils.stream_writer.StreamWriter Stream writer to write the output of the Lambda function to. stderr samcli.lib.utils.stream_writer.StreamWriter Stream writer to write the Lambda runtime logs to. Raises ------ FunctionNotfound When we cannot find a function with the given name """ # Generate the correct configuration based on given inputs function = self.provider.get(function_name) if not function: all_functions = [f.name for f in self.provider.get_all()] available_function_message = "{} not found. Possible options in your template: {}".format( function_name, all_functions) LOG.info(available_function_message) raise FunctionNotFound( "Unable to find a Function with name '{}'".format( function_name)) LOG.debug("Found one Lambda function with name '%s'", function_name) if function.packagetype == ZIP: LOG.info("Invoking %s (%s)", function.handler, function.runtime) elif function.packagetype == IMAGE: if not function.imageuri: raise InvalidIntermediateImageError( f"ImageUri not provided for Function: {function_name} of PackageType: {function.packagetype}" ) LOG.info("Invoking Container created from %s", function.imageuri) config = self.get_invoke_config(function) # Invoke the function try: self.local_runtime.invoke(config, event, debug_context=self.debug_context, stdout=stdout, stderr=stderr) except ContainerResponseException: # NOTE(sriram-mv): This should still result in a exit code zero to avoid regressions. LOG.info("No response from invoke container for %s", function.name) except OSError as os_error: # pylint: disable=no-member if hasattr(os_error, "winerror") and os_error.winerror == 1314: raise NoPrivilegeException( "Administrator, Windows Developer Mode, " "or SeCreateSymbolicLinkPrivilege is required to create symbolic link for files: {}, {}" .format(os_error.filename, os_error.filename2)) from os_error raise