def _locate_layer_from_ref( stack: Stack, layer: Dict, use_raw_codeuri: bool = False, ignore_code_extraction_warnings: bool = False ) -> Optional[LayerVersion]: layer_logical_id = cast(str, layer.get("Ref")) layer_resource = stack.resources.get(layer_logical_id) if not layer_resource or layer_resource.get("Type", "") not in ( SamFunctionProvider.SERVERLESS_LAYER, SamFunctionProvider.LAMBDA_LAYER, ): raise InvalidLayerReference() layer_properties = layer_resource.get("Properties", {}) resource_type = layer_resource.get("Type") compatible_runtimes = layer_properties.get("CompatibleRuntimes") codeuri: Optional[str] = None if resource_type in [ SamFunctionProvider.LAMBDA_LAYER, SamFunctionProvider.SERVERLESS_LAYER ]: code_property_key = SamBaseProvider.CODE_PROPERTY_KEYS[ resource_type] if SamBaseProvider._is_s3_location( layer_properties.get(code_property_key)): # Content can be a dictionary of S3 Bucket/Key or a S3 URI, neither of which are supported if not ignore_code_extraction_warnings: SamFunctionProvider._warn_code_extraction( resource_type, layer_logical_id, code_property_key) return None codeuri = SamBaseProvider._extract_codeuri(layer_properties, code_property_key) if codeuri and not use_raw_codeuri: LOG.debug( "--base-dir is not presented, adjusting uri %s relative to %s", codeuri, stack.location) codeuri = SamLocalStackProvider.normalize_resource_path( stack.location, codeuri) return LayerVersion( layer_logical_id, codeuri, compatible_runtimes, layer_resource.get("Metadata", None), stack_path=stack.stack_path, )
class TestCli(TestCase): def setUp(self): self.template = "template" self.env_vars = "env-vars" self.debug_ports = [123] self.debug_args = "args" self.debugger_path = "/test/path" self.container_env_vars = "container-env-vars" self.docker_volume_basedir = "basedir" self.docker_network = "network" self.log_file = "logfile" self.skip_pull_image = True self.parameter_overrides = {} self.layer_cache_basedir = "/some/layers/path" self.force_image_build = True self.shutdown = True self.region_name = "region" self.profile = "profile" self.warm_containers = None self.debug_function = None self.ctx_mock = Mock() self.ctx_mock.region = self.region_name self.ctx_mock.profile = self.profile self.host = "host" self.port = 123 self.static_dir = "staticdir" @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.lib.local_api_service.LocalApiService") def test_cli_must_setup_context_and_start_service(self, local_api_service_mock, invoke_context_mock): # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() invoke_context_mock.return_value.__enter__.return_value = context_mock service_mock = Mock() local_api_service_mock.return_value = service_mock self.warm_containers = None self.debug_function = None self.call_cli() invoke_context_mock.assert_called_with( template_file=self.template, function_identifier=None, 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, aws_region=self.region_name, aws_profile=self.profile, warm_container_initialization_mode=self.warm_containers, debug_function=self.debug_function, shutdown=self.shutdown, ) local_api_service_mock.assert_called_with( lambda_invoke_context=context_mock, port=self.port, host=self.host, static_dir=self.static_dir ) service_mock.start.assert_called_with() @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") @patch("samcli.commands.local.lib.local_api_service.LocalApiService") def test_must_raise_if_no_api_defined(self, local_api_service_mock, invoke_context_mock): # Mock the __enter__ method to return a object inside a context manager context_mock = Mock() invoke_context_mock.return_value.__enter__.return_value = context_mock service_mock = Mock() local_api_service_mock.return_value = service_mock service_mock.start.side_effect = NoApisDefined("no apis") with self.assertRaises(UserException) as context: self.call_cli() msg = str(context.exception) expected = "Template does not have any APIs connected to Lambda functions" self.assertEqual(msg, expected) @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") def test_must_raise_user_exception_on_invalid_sam_template( self, exeception_to_raise, execption_message, invoke_context_mock ): invoke_context_mock.side_effect = exeception_to_raise with self.assertRaises(UserException) as context: self.call_cli() msg = str(context.exception) expected = execption_message self.assertEqual(msg, expected) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") def test_must_raise_user_exception_on_invalid_env_vars(self, invoke_context_mock): invoke_context_mock.side_effect = OverridesNotWellDefinedError("bad env vars") with self.assertRaises(UserException) as context: self.call_cli() msg = str(context.exception) expected = "bad env vars" self.assertEqual(msg, expected) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") def test_must_raise_user_exception_on_no_free_ports(self, invoke_context_mock): invoke_context_mock.side_effect = ContainerNotStartableException("no free ports on host to bind with container") with self.assertRaises(UserException) as context: self.call_cli() msg = str(context.exception) expected = "no free ports on host to bind with container" self.assertEqual(msg, expected) @patch("samcli.commands.local.cli_common.invoke_context.InvokeContext") 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 call_cli(self): start_api_cli( ctx=self.ctx_mock, host=self.host, port=self.port, static_dir=self.static_dir, template=self.template, 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, warm_containers=self.warm_containers, debug_function=self.debug_function, shutdown=self.shutdown, )
def _parse_layer_info(list_of_layers, resources, ignore_code_extraction_warnings=False): """ Creates a list of Layer objects that are represented by the resources and the list of layers Parameters ---------- list_of_layers List(str) List of layers that are defined within the Layers Property on a function resources dict The Resources dictionary defined in a template Returns ------- List(samcli.commands.local.lib.provider.Layer) List of the Layer objects created from the template and layer list defined on the function. The order of the layers does not change. I.E: list_of_layers = ["layer1", "layer2"] the return would be [Layer("layer1"), Layer("layer2")] """ layers = [] for layer in list_of_layers: if layer == "arn:aws:lambda:::awslayer:AmazonLinux1803": LOG.debug("Skipped arn:aws:lambda:::awslayer:AmazonLinux1803 as the containers are AmazonLinux1803") continue if layer == "arn:aws:lambda:::awslayer:AmazonLinux1703": raise InvalidLayerVersionArn( "Building and invoking locally only supports AmazonLinux1803. See " "https://aws.amazon.com/blogs/compute/upcoming-updates-to-the-aws-lambda-execution-environment/ for more detials." ) # noqa: E501 # If the layer is a string, assume it is the arn if isinstance(layer, str): layers.append(LayerVersion(layer, None)) continue # In the list of layers that is defined within a template, you can reference a LayerVersion resource. # When running locally, we need to follow that Ref so we can extract the local path to the layer code. if isinstance(layer, dict) and layer.get("Ref"): layer_logical_id = layer.get("Ref") layer_resource = resources.get(layer_logical_id) if not layer_resource or layer_resource.get("Type", "") not in ( SamFunctionProvider.SERVERLESS_LAYER, SamFunctionProvider.LAMBDA_LAYER, ): raise InvalidLayerReference() layer_properties = layer_resource.get("Properties", {}) resource_type = layer_resource.get("Type") compatible_runtimes = layer_properties.get("CompatibleRuntimes") codeuri = None if resource_type == SamFunctionProvider.LAMBDA_LAYER: codeuri = SamFunctionProvider._extract_lambda_function_code(layer_properties, "Content") if resource_type == SamFunctionProvider.SERVERLESS_LAYER: codeuri = SamFunctionProvider._extract_sam_function_codeuri( layer_logical_id, layer_properties, "ContentUri", ignore_code_extraction_warnings ) layers.append( LayerVersion(layer_logical_id, codeuri, compatible_runtimes, layer_resource.get("Metadata", None)) ) return layers
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 _parse_layer_info( stack: Stack, list_of_layers: List[Any], use_raw_codeuri: bool = False, ignore_code_extraction_warnings: bool = False, ) -> List[LayerVersion]: """ Creates a list of Layer objects that are represented by the resources and the list of layers Parameters ---------- stack : Stack The stack the layer is defined in list_of_layers : List[Any] List of layers that are defined within the Layers Property on a function, layer can be defined as string or Dict, in case customers define it in other types, use "Any" here. use_raw_codeuri : bool Do not resolve adjust core_uri based on the template path, use the raw uri. ignore_code_extraction_warnings : bool Whether to print warning when codeuri is not a local pth Returns ------- List(samcli.commands.local.lib.provider.Layer) List of the Layer objects created from the template and layer list defined on the function. The order of the layers does not change. I.E: list_of_layers = ["layer1", "layer2"] the return would be [Layer("layer1"), Layer("layer2")] """ layers = [] for layer in list_of_layers: if layer == "arn:aws:lambda:::awslayer:AmazonLinux1803": LOG.debug("Skipped arn:aws:lambda:::awslayer:AmazonLinux1803 as the containers are AmazonLinux1803") continue if layer == "arn:aws:lambda:::awslayer:AmazonLinux1703": raise InvalidLayerVersionArn( "Building and invoking locally only supports AmazonLinux1803. See " "https://aws.amazon.com/blogs/compute/upcoming-updates-to-the-aws-lambda-execution-environment/ " "for more detials." ) # noqa: E501 # If the layer is a string, assume it is the arn if isinstance(layer, str): layers.append( LayerVersion( layer, None, stack_path=stack.stack_path, ) ) continue # In the list of layers that is defined within a template, you can reference a LayerVersion resource. # When running locally, we need to follow that Ref so we can extract the local path to the layer code. if isinstance(layer, dict) and layer.get("Ref"): layer_logical_id = cast(str, layer.get("Ref")) layer_resource = stack.resources.get(layer_logical_id) if not layer_resource or layer_resource.get("Type", "") not in ( SamFunctionProvider.SERVERLESS_LAYER, SamFunctionProvider.LAMBDA_LAYER, ): raise InvalidLayerReference() layer_properties = layer_resource.get("Properties", {}) resource_type = layer_resource.get("Type") compatible_runtimes = layer_properties.get("CompatibleRuntimes") codeuri: Optional[str] = None if resource_type == SamFunctionProvider.LAMBDA_LAYER: codeuri = SamFunctionProvider._extract_lambda_function_code(layer_properties, "Content") if resource_type == SamFunctionProvider.SERVERLESS_LAYER: codeuri = SamFunctionProvider._extract_sam_function_codeuri( layer_logical_id, layer_properties, "ContentUri", ignore_code_extraction_warnings ) if codeuri and not use_raw_codeuri: LOG.debug("--base-dir is presented not, adjusting uri %s relative to %s", codeuri, stack.location) codeuri = SamLocalStackProvider.normalize_resource_path(stack.location, codeuri) layers.append( LayerVersion( layer_logical_id, codeuri, compatible_runtimes, layer_resource.get("Metadata", None), stack_path=stack.stack_path, ) ) return layers