def test_layers_created_from_template_resources(self): resources = { "Layer": {"Type": "AWS::Lambda::LayerVersion", "Properties": {"Content": {"Bucket": "bucket"}}}, "ServerlessLayer": {"Type": "AWS::Serverless::LayerVersion", "Properties": {"ContentUri": "/somepath"}}, } list_of_layers = [ {"Ref": "Layer"}, {"Ref": "ServerlessLayer"}, "arn:aws:lambda:region:account-id:layer:layer-name:1", {"NonRef": "Something"}, ] actual = SamFunctionProvider._parse_layer_info( Mock(stack_path=STACK_PATH, location="template.yaml", resources=resources), list_of_layers ) for (actual_layer, expected_layer) in zip( actual, [ LayerVersion("Layer", ".", stack_path=STACK_PATH), LayerVersion("ServerlessLayer", "/somepath", stack_path=STACK_PATH), LayerVersion("arn:aws:lambda:region:account-id:layer:layer-name:1", None, stack_path=STACK_PATH), ], ): self.assertEqual(actual_layer, expected_layer)
def download(self, layer: LayerVersion, force=False) -> LayerVersion: """ Download a given layer to the local cache. Parameters ---------- layer samcli.commands.local.lib.provider.Layer Layer representing the layer to be downloaded. force bool True to download the layer even if it exists already on the system Returns ------- Path Path object that represents where the layer is download to """ if layer.is_defined_within_template: LOG.info("%s is a local Layer in the template", layer.name) # the template file containing the layer might not be in the same directory as root template file # therefore we need to join the path of template directory and codeuri in case codeuri is a relative path. try: stack = next(stack for stack in self._stacks if stack.stack_path == layer.stack_path) except StopIteration as ex: raise RuntimeError( f"Cannot find stack that matches layer's stack_path {layer.stack_path}" ) from ex codeuri = (layer.codeuri if os.path.isabs(layer.codeuri) else os.path.normpath( os.path.join(os.path.dirname(stack.location), layer.codeuri))) layer.codeuri = resolve_code_path(self.cwd, codeuri) return layer layer_path = Path(self.layer_cache).resolve().joinpath(layer.name) is_layer_downloaded = self._is_layer_cached(layer_path) layer.codeuri = str(layer_path) if is_layer_downloaded and not force: LOG.info("%s is already cached. Skipping download", layer.arn) return layer layer_zip_path = layer.codeuri + ".zip" layer_zip_uri = self._fetch_layer_uri(layer) unzip_from_uri( layer_zip_uri, layer_zip_path, unzip_output_dir=layer.codeuri, progressbar_label="Downloading {}".format(layer.layer_arn), ) return layer
def test_layer_version_raises_unsupported_intrinsic(self): intrinsic_arn = { "Fn::Sub": ["arn:aws:lambda:region:account-id:layer:{layer_name}:1", {"layer_name": "layer-name"}] } with self.assertRaises(UnsupportedIntrinsic): LayerVersion(intrinsic_arn, ".")
def setUp(self): self.manager_mock = Mock() self.name = "name" self.lang = "runtime" self.handler = "handler" self.code_path = "code-path" self.layer1_code_path = "layer1-code-path" self.layer1_arn = "layer1-arn" self.layers = [ LayerVersion(arn=self.layer1_arn, codeuri=self.layer1_code_path, compatible_runtimes=self.lang) ] self.imageuri = None self.packagetype = ZIP self.imageconfig = None self.func_config = FunctionConfig( self.name, self.lang, self.handler, self.imageuri, self.imageconfig, self.packagetype, self.code_path, self.layers, )
def setUp(self): self.manager_mock = Mock() lambda_image_mock = Mock() self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock) self.observer_mock = Mock() self.runtime._observer = self.observer_mock self.lang = "runtime" self.handler = "handler" self.imageuri = None self.imageconfig = None self.func1_name = "func1_name" self.func1_code_path = "func1_code_path" self.func2_name = "func2_name" self.func2_code_path = "func2_code_path" self.common_layer_code_path = "layer1-code-path" self.common_layer_arn = "layer1-arn" self.common_layers = [ LayerVersion(arn=self.common_layer_arn, codeuri=self.common_layer_code_path, compatible_runtimes=self.lang) ] self.func_config1 = FunctionConfig( self.func1_name, self.lang, self.handler, self.imageuri, self.imageconfig, ZIP, self.func1_code_path, self.common_layers, ) self.func_config2 = FunctionConfig( self.func2_name, self.lang, self.handler, self.imageuri, self.imageconfig, IMAGE, self.func2_code_path, self.common_layers, ) self.func1_container_mock = Mock() self.func2_container_mock = Mock() self.runtime._containers = { self.func1_name: self.func1_container_mock, self.func2_name: self.func2_container_mock, }
def test_must_ignore_opt_in_AmazonLinux1803_layer(self): resources = {} list_of_layers = [ "arn:aws:lambda:region:account-id:layer:layer-name:1", "arn:aws:lambda:::awslayer:AmazonLinux1803", ] actual = SamFunctionProvider._parse_layer_info(list_of_layers, resources) for (actual_layer, expected_layer) in zip( actual, [LayerVersion("arn:aws:lambda:region:account-id:layer:layer-name:1", None)] ): self.assertEqual(actual_layer, expected_layer)
def download(self, layer: LayerVersion, force=False) -> LayerVersion: """ Download a given layer to the local cache. Parameters ---------- layer samcli.commands.local.lib.provider.Layer Layer representing the layer to be downloaded. force bool True to download the layer even if it exists already on the system Returns ------- Path Path object that represents where the layer is download to """ if layer.is_defined_within_template: LOG.info("%s is a local Layer in the template", layer.name) layer.codeuri = resolve_code_path(self.cwd, layer.codeuri) return layer layer_path = Path(self.layer_cache).resolve().joinpath(layer.name) is_layer_downloaded = self._is_layer_cached(layer_path) layer.codeuri = str(layer_path) if is_layer_downloaded and not force: LOG.info("%s is already cached. Skipping download", layer.arn) return layer layer_zip_path = layer.codeuri + ".zip" layer_zip_uri = self._fetch_layer_uri(layer) unzip_from_uri( layer_zip_uri, layer_zip_path, unzip_output_dir=layer.codeuri, progressbar_label="Downloading {}".format(layer.layer_arn), ) return layer
def test_must_ignore_opt_in_AmazonLinux1803_layer(self): resources = {} list_of_layers = [ "arn:aws:lambda:region:account-id:layer:layer-name:1", "arn:aws:lambda:::awslayer:AmazonLinux1803", ] actual = SamFunctionProvider._parse_layer_info( Mock(stack_path=STACK_PATH, location="template.yaml", resources=resources), list_of_layers ) for (actual_layer, expected_layer) in zip( actual, [LayerVersion("arn:aws:lambda:region:account-id:layer:layer-name:1", None, stack_path=STACK_PATH)] ): self.assertEqual(actual_layer, expected_layer)
def test_codeuri_is_setable(self): layer_version = LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None) layer_version.codeuri = "./some_value" self.assertEqual(layer_version.codeuri, "./some_value")
def test_layer_build_method_returned(self): layer_version = LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None, {"BuildMethod": "dummy_build_method"}) self.assertEqual(layer_version.build_method, "dummy_build_method")
def test_layer_arn_returned(self): layer_version = LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None) self.assertEqual(layer_version.layer_arn, "arn:aws:lambda:region:account-id:layer:layer-name")
def test_invalid_arn(self, arn): with self.assertRaises(InvalidLayerVersionArn): LayerVersion(arn, None)
class TestSamLayerProvider(TestCase): TEMPLATE = { "Resources": { "ServerlessLayer": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, "Metadata": { "BuildMethod": "python3.8" }, }, "LambdaLayer": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, "Metadata": { "BuildMethod": "python3.8" }, }, "ServerlessLayerNoBuild": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "LambdaLayerNoBuild": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "ServerlessLayerS3Content": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "s3://dummy-bucket/my-layer.zip", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "LambdaLayerS3Content": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": { "S3Bucket": "dummy-bucket", "S3Key": "layer.zip" }, "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "SamFunc": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": "s3://bucket/key", "Runtime": "nodejs4.3", "Handler": "index.handler", }, }, "ChildStack": { "Type": "AWS::Serverless::Application", "Properties": { "Location": "./child.yaml", }, }, } } CHILD_TEMPLATE = { "Resources": { "SamLayerInChild": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "PyLayer", "CompatibleRuntimes": ["python3.8", "python3.6"], }, "Metadata": { "BuildMethod": "python3.8" }, }, } } def setUp(self): self.parameter_overrides = {} root_stack = Stack("", "", "template.yaml", self.parameter_overrides, self.TEMPLATE) child_stack = Stack("", "ChildStack", "./child/template.yaml", None, self.CHILD_TEMPLATE) with patch("samcli.lib.providers.sam_stack_provider.get_template_data" ) as get_template_data_mock: get_template_data_mock.side_effect = lambda t: { "template.yaml": self.TEMPLATE, "./child/template.yaml": self.CHILD_TEMPLATE, } self.provider = SamLayerProvider([root_stack, child_stack]) @parameterized.expand([ ( "ServerlessLayer", LayerVersion( "ServerlessLayer", "PyLayer", ["python3.8", "python3.6"], {"BuildMethod": "python3.8"}, stack_path="", ), ), ( "LambdaLayer", LayerVersion( "LambdaLayer", "PyLayer", ["python3.8", "python3.6"], {"BuildMethod": "python3.8"}, stack_path="", ), ), ( "ServerlessLayerNoBuild", LayerVersion("ServerlessLayerNoBuild", "PyLayer", ["python3.8", "python3.6"], None, stack_path=""), ), ( "LambdaLayerNoBuild", LayerVersion("LambdaLayerNoBuild", "PyLayer", ["python3.8", "python3.6"], None, stack_path=""), ), ("ServerlessLayerS3Content", None), # codeuri is a s3 location, ignored ("LambdaLayerS3Content", None), # codeuri is a s3 location, ignored ( posixpath.join("ChildStack", "SamLayerInChild"), LayerVersion( "SamLayerInChild", os.path.join("child", "PyLayer"), ["python3.8", "python3.6"], {"BuildMethod": "python3.8"}, stack_path="ChildStack", ), ), ]) def test_get_must_return_each_layer(self, name, expected_output): actual = self.provider.get(name) self.assertEqual(expected_output, actual) def test_get_all_must_return_all_layers(self): result = [ posixpath.join(f.stack_path, f.arn) for f in self.provider.get_all() ] expected = [ "ServerlessLayer", "LambdaLayer", "ServerlessLayerNoBuild", "LambdaLayerNoBuild", posixpath.join("ChildStack", "SamLayerInChild"), ] self.assertEqual(expected, result) def test_provider_ignores_non_layer_resource(self): self.assertIsNone(self.provider.get("SamFunc")) def test_fails_with_empty_name(self): with self.assertRaises(ValueError): self.provider.get("")
class TestSamLayerProvider(TestCase): TEMPLATE = { "Resources": { "ServerlessLayer": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, "Metadata": {"BuildMethod": "python3.8"}, }, "LambdaLayer": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, "Metadata": {"BuildMethod": "python3.8"}, }, "ServerlessLayerNoBuild": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "LambdaLayerNoBuild": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": "PyLayer/", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "ServerlessLayerS3Content": { "Type": "AWS::Serverless::LayerVersion", "Properties": { "LayerName": "Layer1", "ContentUri": "s3://dummy-bucket/my-layer.zip", "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "LambdaLayerS3Content": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "LayerName": "Layer1", "Content": {"S3Bucket": "dummy-bucket", "S3Key": "layer.zip"}, "CompatibleRuntimes": ["python3.8", "python3.6"], }, }, "SamFunc": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": "s3://bucket/key", "Runtime": "nodejs4.3", "Handler": "index.handler", }, }, } } def setUp(self): self.parameter_overrides = {} self.provider = SamLayerProvider(self.TEMPLATE, parameter_overrides=self.parameter_overrides) @parameterized.expand( [ ("ServerlessLayer", LayerVersion("ServerlessLayer", "PyLayer/", {"BuildMethod": "python3.8"})), ("LambdaLayer", LayerVersion("LambdaLayer", "PyLayer/", {"BuildMethod": "python3.8"})), ("ServerlessLayerNoBuild", LayerVersion("ServerlessLayerNoBuild", "PyLayer/", None)), ("LambdaLayerNoBuild", LayerVersion("LambdaLayerNoBuild", "PyLayer/", None)), ("ServerlessLayerS3Content", LayerVersion("ServerlessLayerS3Content", ".", None)), ("LambdaLayerS3Content", LayerVersion("LambdaLayerS3Content", ".", None)), ] ) def test_get_must_return_each_layer(self, name, expected_output): actual = self.provider.get(name) self.assertEqual(actual, expected_output) def test_get_all_must_return_all_layers(self): result = [f.arn for f in self.provider.get_all()] expected = [ "ServerlessLayer", "LambdaLayer", "ServerlessLayerNoBuild", "LambdaLayerNoBuild", "ServerlessLayerS3Content", "LambdaLayerS3Content", ] self.assertEqual(result, expected) def test_provider_ignores_non_layer_resource(self): self.assertIsNone(self.provider.get("SamFunc")) def test_fails_with_empty_name(self): with self.assertRaises(ValueError): self.provider.get("")
def test_name_is_computed(self): layer_version = LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None) self.assertEqual(layer_version.name, "layer-name-1-8cebcd0539")
def test_layer_version_is_defined_in_template(self): layer_version = LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", ".") self.assertTrue(layer_version.is_defined_within_template)
def test_invalid_arn(self, arn): layer = LayerVersion( arn, None) # creation of layer does not raise exception with self.assertRaises(InvalidLayerVersionArn): layer.version, layer.name