def test_must_return_function_value(self): provider = SamFunctionProvider({}) provider.functions = { "func1": "value" } # Cheat a bit here by setting the value of this property directly self.assertEqual("value", provider.get("func1"))
def test_must_default_missing_code_uri(self): name = "myname" properties = {"Runtime": "myruntime"} result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEqual(result.codeuri, ".") # Default value
def test_must_handle_code_s3_uri(self): name = "myname" properties = {"CodeUri": "s3://bucket/key"} result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEqual(result.codeuri, ".") # Default value
def test_must_skip_unknown_resource(self): resources = {"Func1": {"Type": "AWS::SomeOther::Function", "Properties": {"a": "b"}}} expected = {} result = SamFunctionProvider._extract_functions(resources) self.assertEqual(expected, result)
def test_must_convert(self): name = "myname" properties = { "CodeUri": "/usr/local", "Runtime": "myruntime", "MemorySize": "mymemorysize", "Timeout": "mytimeout", "Handler": "myhandler", "Environment": "myenvironment", "Role": "myrole", "Layers": ["Layer1", "Layer2"] } expected = Function(name="myname", runtime="myruntime", memory="mymemorysize", timeout="mytimeout", handler="myhandler", codeuri="/usr/local", environment="myenvironment", rolearn="myrole", layers=["Layer1", "Layer2"]) result = SamFunctionProvider._convert_sam_function_resource( name, properties, ["Layer1", "Layer2"]) self.assertEquals(expected, result)
def test_must_fail_with_InvalidSamTemplateException(self): name = "myname" properties = { "CodeUri": "/usr/local", "Runtime": "myruntime", "MemorySize": "mymemorysize", "Timeout": "timeout", "Handler": "myhandler", "Environment": "myenvironment", "Role": "myrole", "Layers": ["Layer1", "Layer2"], } with self.assertRaises(InvalidSamTemplateException): SamFunctionProvider._convert_sam_function_resource(name, properties, ["Layer1", "Layer2"])
def test_must_convert(self): name = "myname" properties = { "Code": { "Bucket": "bucket" }, "Runtime": "myruntime", "MemorySize": "mymemorysize", "Timeout": "30", "Handler": "myhandler", "Environment": "myenvironment", "Role": "myrole", "Layers": ["Layer1", "Layer2"], } expected = Function( name="myname", runtime="myruntime", memory="mymemorysize", timeout="30", handler="myhandler", codeuri=".", environment="myenvironment", rolearn="myrole", layers=["Layer1", "Layer2"], ) result = SamFunctionProvider._convert_lambda_function_resource( name, properties, ["Layer1", "Layer2"]) self.assertEqual(expected, result)
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(list_of_layers, resources) for (actual_layer, expected_layer) in zip(actual, [LayerVersion("Layer", "."), LayerVersion("ServerlessLayer", "/somepath"), LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None)]): self.assertEquals(actual_layer, expected_layer)
def test_must_convert(self): name = "myname" properties = { "Code": { "Bucket": "bucket" }, "Runtime": "myruntime", "MemorySize": "mymemorysize", "Timeout": "mytimeout", "Handler": "myhandler", "Environment": "myenvironment", "Role": "myrole", "Layers": ["Layer1", "Layer2"] } expected = Function( name="myname", runtime="myruntime", memory="mymemorysize", timeout="mytimeout", handler="myhandler", codeuri=".", environment="myenvironment", rolearn="myrole", layers=["Layer1", "Layer2"] ) result = SamFunctionProvider._convert_lambda_function_resource(name, properties, ["Layer1", "Layer2"]) self.assertEquals(expected, result)
def test_must_handle_code_s3_uri(self): name = "myname" properties = { "CodeUri": "s3://bucket/key" } result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEquals(result.codeuri, ".") # Default value
def test_must_default_missing_code_uri(self): name = "myname" properties = { "Runtime": "myruntime" } result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEquals(result.codeuri, ".") # Default value
def test_must_extract_functions(self, SamBaseProviderMock, extract_mock): extract_result = {"foo": "bar"} extract_mock.return_value = extract_result template = {"Resources": {"a": "b"}} SamBaseProviderMock.get_template.return_value = template provider = SamFunctionProvider(template) extract_mock.assert_called_with({"a": "b"}) SamBaseProviderMock.get_template.assert_called_with(template) self.assertEquals(provider.functions, extract_result)
def test_must_default_to_empty_resources(self, SamBaseProviderMock, extract_mock): extract_result = {"foo": "bar"} extract_mock.return_value = extract_result template = {"a": "b"} # Template does *not* have 'Resources' key SamBaseProviderMock.get_template.return_value = template provider = SamFunctionProvider(template) extract_mock.assert_called_with({}) # Empty Resources value must be passed self.assertEquals(provider.functions, extract_result) self.assertEquals(provider.resources, {})
def test_must_work_for_lambda_function(self, convert_mock): convertion_result = "some result" convert_mock.return_value = convertion_result resources = {"Func1": {"Type": "AWS::Lambda::Function", "Properties": {"a": "b"}}} expected = {"Func1": "some result"} result = SamFunctionProvider._extract_functions(resources) self.assertEqual(expected, result) convert_mock.assert_called_with("Func1", {"a": "b"}, [])
def test_return_empty_list_on_no_layers(self): resources = { "Function": { "Type": "AWS::Serverless::Function", "Properties": {} } } actual = SamFunctionProvider._parse_layer_info([], resources) self.assertEqual(actual, [])
def test_return_empty_list_on_no_layers(self): resources = { "Function": { "Type": "AWS::Serverless::Function", "Properties": { } } } actual = SamFunctionProvider._parse_layer_info([], resources) self.assertEquals(actual, [])
def test_must_handle_code_dict(self): name = "myname" properties = { "CodeUri": { # CodeUri is some dictionary "a": "b" } } result = SamFunctionProvider._convert_sam_function_resource(name, properties) self.assertEquals(result.codeuri, ".") # Default value
def test_must_handle_code_dict(self): name = "myname" properties = { "CodeUri": { # CodeUri is some dictionary "a": "b" } } result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEquals(result.codeuri, ".") # Default value
def test_must_skip_unknown_resource(self): resources = { "Func1": { "Type": "AWS::SomeOther::Function", "Properties": {"a": "b"} } } expected = {} result = SamFunctionProvider._extract_functions(resources) self.assertEquals(expected, result)
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 __enter__(self): try: self._template_dict = get_template_data(self._template_file) except ValueError as ex: raise UserException(str(ex)) self._function_provider = SamFunctionProvider( self._template_dict, self._parameter_overrides) if not self._base_dir: # Base directory, if not provided, is the directory containing the template self._base_dir = str( pathlib.Path(self._template_file).resolve().parent) self._build_dir = self._setup_build_dir(self._build_dir, self._clean) if self._use_container: self._container_manager = ContainerManager( docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image) return self
def test_must_work_with_no_properties(self, convert_mock): convertion_result = "some result" convert_mock.return_value = convertion_result resources = { "Func1": { "Type": "AWS::Serverless::Function" # No Properties } } expected = {"Func1": "some result"} result = SamFunctionProvider._extract_functions(resources) self.assertEqual(expected, result) convert_mock.assert_called_with("Func1", {}, [])
def test_must_work_for_lambda_function(self, convert_mock): convertion_result = "some result" convert_mock.return_value = convertion_result resources = { "Func1": { "Type": "AWS::Lambda::Function", "Properties": {"a": "b"} } } expected = { "Func1": "some result" } result = SamFunctionProvider._extract_functions(resources) self.assertEquals(expected, result) convert_mock.assert_called_with('Func1', {"a": "b"}, [])
def test_must_work_with_no_properties(self, convert_mock): convertion_result = "some result" convert_mock.return_value = convertion_result resources = { "Func1": { "Type": "AWS::Serverless::Function" # No Properties } } expected = { "Func1": "some result" } result = SamFunctionProvider._extract_functions(resources) self.assertEquals(expected, result) convert_mock.assert_called_with('Func1', {}, [])
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(list_of_layers, resources) for (actual_layer, expected_layer) in zip( actual, [ LayerVersion("Layer", "."), LayerVersion("ServerlessLayer", "/somepath"), LayerVersion( "arn:aws:lambda:region:account-id:layer:layer-name:1", None), ], ): self.assertEqual(actual_layer, expected_layer)
def test_must_skip_non_existent_properties(self): name = "myname" properties = {"Code": {"Bucket": "bucket"}} expected = Function(name="myname", runtime=None, memory=None, timeout=None, handler=None, codeuri=".", environment=None, rolearn=None, layers=[]) result = SamFunctionProvider._convert_lambda_function_resource( name, properties, []) self.assertEquals(expected, result)
def test_must_skip_non_existent_properties(self): name = "myname" properties = {"CodeUri": "/usr/local"} expected = Function( name="myname", runtime=None, memory=None, timeout=None, handler=None, codeuri="/usr/local", environment=None, rolearn=None, layers=[], ) result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEqual(expected, result)
def test_must_skip_non_existent_properties(self): name = "myname" properties = { "CodeUri": "/usr/local" } expected = Function( name="myname", runtime=None, memory=None, timeout=None, handler=None, codeuri="/usr/local", environment=None, rolearn=None, layers=[] ) result = SamFunctionProvider._convert_sam_function_resource(name, properties, []) self.assertEquals(expected, result)
def test_raise_on_invalid_name(self): provider = SamFunctionProvider({}) with self.assertRaises(ValueError): provider.get(None)
def test_raise_on_AmazonLinux1703_layer_provided(self, resources, layer_reference): with self.assertRaises(InvalidLayerVersionArn): SamFunctionProvider._parse_layer_info([layer_reference], resources)
def test_raise_on_invalid_layer_resource(self, resources, layer_reference): with self.assertRaises(InvalidLayerReference): SamFunctionProvider._parse_layer_info([layer_reference], resources)
def test_must_return_function_value(self): provider = SamFunctionProvider({}) provider.functions = {"func1": "value"} # Cheat a bit here by setting the value of this property directly self.assertEquals("value", provider.get("func1"))
def test_must_work_with_no_functions(self): provider = SamFunctionProvider({}) result = [f for f in provider.get_all()] self.assertEqual(result, [])
class TestSamFunctionProviderEndToEnd(TestCase): """ Test all public methods with an input template """ TEMPLATE = { "Resources": { "SamFunc1": { "Type": "AWS::Serverless::Function", "Properties": { "CodeUri": "/usr/foo/bar", "Runtime": "nodejs4.3", "Handler": "index.handler" }, }, "SamFunc2": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": "s3://bucket/key", "Runtime": "nodejs4.3", "Handler": "index.handler", }, }, "SamFunc3": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": { "Bucket": "bucket", "Key": "key" }, "Runtime": "nodejs4.3", "Handler": "index.handler", }, }, "LambdaFunc1": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "S3Bucket": "bucket", "S3Key": "key" }, "Runtime": "nodejs4.3", "Handler": "index.handler", }, }, "LambdaFuncWithLocalPath": { "Type": "AWS::Lambda::Function", "Properties": { "Code": "./some/path/to/code", "Runtime": "nodejs4.3", "Handler": "index.handler" }, }, "OtherResource": { "Type": "AWS::Serverless::Api", "Properties": { "StageName": "prod", "DefinitionUri": "s3://bucket/key" }, }, } } EXPECTED_FUNCTIONS = ["SamFunc1", "SamFunc2", "SamFunc3", "LambdaFunc1"] def setUp(self): self.parameter_overrides = {} self.provider = SamFunctionProvider( self.TEMPLATE, parameter_overrides=self.parameter_overrides) @parameterized.expand([ ( "SamFunc1", Function( name="SamFunc1", runtime="nodejs4.3", handler="index.handler", codeuri="/usr/foo/bar", memory=None, timeout=None, environment=None, rolearn=None, layers=[], ), ), ( "SamFunc2", Function( name="SamFunc2", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[], ), ), ( "SamFunc3", Function( name="SamFunc3", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[], ), ), ( "LambdaFunc1", Function( name="LambdaFunc1", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[], ), ), ( "LambdaFuncWithLocalPath", Function( name="LambdaFuncWithLocalPath", runtime="nodejs4.3", handler="index.handler", codeuri="./some/path/to/code", memory=None, timeout=None, environment=None, rolearn=None, layers=[], ), ), ]) def test_get_must_return_each_function(self, name, expected_output): actual = self.provider.get(name) self.assertEqual(actual, expected_output) def test_get_all_must_return_all_functions(self): result = {f.name for f in self.provider.get_all()} expected = { "SamFunc1", "SamFunc2", "SamFunc3", "LambdaFunc1", "LambdaFuncWithLocalPath" } self.assertEqual(result, expected)
def setUp(self): self.provider = SamFunctionProvider(self.TEMPLATE)
class BuildContext(object): # Build directories need not be world writable. # This is usually a optimal permission for directories _BUILD_DIR_PERMISSIONS = 0o755 def __init__(self, function_identifier, template_file, base_dir, build_dir, mode, manifest_path=None, clean=False, use_container=False, parameter_overrides=None, docker_network=None, skip_pull_image=False): self._function_identifier = function_identifier self._template_file = template_file self._base_dir = base_dir self._build_dir = build_dir self._manifest_path = manifest_path self._clean = clean self._use_container = use_container self._parameter_overrides = parameter_overrides self._docker_network = docker_network self._skip_pull_image = skip_pull_image self._mode = mode self._function_provider = None self._template_dict = None self._app_builder = None self._container_manager = None def __enter__(self): try: self._template_dict = get_template_data(self._template_file) except ValueError as ex: raise UserException(str(ex)) self._function_provider = SamFunctionProvider( self._template_dict, self._parameter_overrides) if not self._base_dir: # Base directory, if not provided, is the directory containing the template self._base_dir = str( pathlib.Path(self._template_file).resolve().parent) self._build_dir = self._setup_build_dir(self._build_dir, self._clean) if self._use_container: self._container_manager = ContainerManager( docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image) return self def __exit__(self, *args): pass @staticmethod def _setup_build_dir(build_dir, clean): build_path = pathlib.Path(build_dir) if os.path.abspath(str(build_path)) == os.path.abspath( str(pathlib.Path.cwd())): exception_message = "Failing build: Running a build with build-dir as current working directory is extremely dangerous since the build-dir contents is first removed. This is no longer supported, please remove the '--build-dir' option from the command to allow the build artifacts to be placed in the directory your template is in." raise InvalidBuildDirException(exception_message) if build_path.exists() and os.listdir(build_dir) and clean: # build folder contains something inside. Clear everything. shutil.rmtree(build_dir) build_path.mkdir(mode=BuildContext._BUILD_DIR_PERMISSIONS, parents=True, exist_ok=True) # ensure path resolving is done after creation: https://bugs.python.org/issue32434 return str(build_path.resolve()) @property def container_manager(self): return self._container_manager @property def function_provider(self): return self._function_provider @property def template_dict(self): return self._template_dict @property def build_dir(self): return self._build_dir @property def base_dir(self): return self._base_dir @property def use_container(self): return self._use_container @property def output_template_path(self): return os.path.join(self._build_dir, "template.yaml") @property def original_template_path(self): return os.path.abspath(self._template_file) @property def manifest_path_override(self): if self._manifest_path: return os.path.abspath(self._manifest_path) return None @property def mode(self): return self._mode @property def functions_to_build(self): if self._function_identifier: function = self._function_provider.get(self._function_identifier) if not function: all_functions = [ f.name for f in self._function_provider.get_all() ] available_function_message = "{} not found. Possible options in your template: {}" \ .format(self._function_identifier, all_functions) LOG.info(available_function_message) raise FunctionNotFound( "Unable to find a Function with name '%s'", self._function_identifier) return [function] return self._function_provider.get_all()
def test_return_none_if_function_not_found(self): provider = SamFunctionProvider({}) self.assertIsNone(provider.get("somefunc"), "Must return None when Function is not found")
def setUp(self): self.parameter_overrides = {} self.provider = SamFunctionProvider( self.TEMPLATE, parameter_overrides=self.parameter_overrides)
class TestSamFunctionProviderEndToEnd(TestCase): """ Test all public methods with an input template """ TEMPLATE = { "Resources": { "SamFunc1": { "Type": "AWS::Serverless::Function", "Properties": { "CodeUri": "/usr/foo/bar", "Runtime": "nodejs4.3", "Handler": "index.handler" } }, "SamFunc2": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": "s3://bucket/key", "Runtime": "nodejs4.3", "Handler": "index.handler" } }, "SamFunc3": { "Type": "AWS::Serverless::Function", "Properties": { # CodeUri is unsupported S3 location "CodeUri": { "Bucket": "bucket", "Key": "key" }, "Runtime": "nodejs4.3", "Handler": "index.handler" } }, "LambdaFunc1": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "S3Bucket": "bucket", "S3Key": "key" }, "Runtime": "nodejs4.3", "Handler": "index.handler" } }, "LambdaFuncWithLocalPath": { "Type": "AWS::Lambda::Function", "Properties": { "Code": "./some/path/to/code", "Runtime": "nodejs4.3", "Handler": "index.handler" } }, "OtherResource": { "Type": "AWS::Serverless::Api", "Properties": { "StageName": "prod", "DefinitionUri": "s3://bucket/key" } } } } EXPECTED_FUNCTIONS = ["SamFunc1", "SamFunc2", "SamFunc3", "LambdaFunc1"] def setUp(self): self.parameter_overrides = {} self.provider = SamFunctionProvider(self.TEMPLATE, parameter_overrides=self.parameter_overrides) @parameterized.expand([ ("SamFunc1", Function( name="SamFunc1", runtime="nodejs4.3", handler="index.handler", codeuri="/usr/foo/bar", memory=None, timeout=None, environment=None, rolearn=None, layers=[] )), ("SamFunc2", Function( name="SamFunc2", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[] )), ("SamFunc3", Function( name="SamFunc3", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[] )), ("LambdaFunc1", Function( name="LambdaFunc1", runtime="nodejs4.3", handler="index.handler", codeuri=".", memory=None, timeout=None, environment=None, rolearn=None, layers=[] )), ("LambdaFuncWithLocalPath", Function( name="LambdaFuncWithLocalPath", runtime="nodejs4.3", handler="index.handler", codeuri="./some/path/to/code", memory=None, timeout=None, environment=None, rolearn=None, layers=[] )) ]) def test_get_must_return_each_function(self, name, expected_output): actual = self.provider.get(name) self.assertEquals(actual, expected_output) def test_get_all_must_return_all_functions(self): result = {f.name for f in self.provider.get_all()} expected = {"SamFunc1", "SamFunc2", "SamFunc3", "LambdaFunc1", "LambdaFuncWithLocalPath"} self.assertEquals(result, expected)
def setUp(self): self.parameter_overrides = {} self.provider = SamFunctionProvider(self.TEMPLATE, parameter_overrides=self.parameter_overrides)
def test_must_work_with_no_functions(self): provider = SamFunctionProvider({}) result = [f for f in provider.get_all()] self.assertEquals(result, [])