def __enter__(self): self._template_dict = get_template_data(self._template_file) self._function_provider = SamFunctionProvider( self._template_dict, self._parameter_overrides) self._layer_provider = SamLayerProvider(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._cached: cache_path = pathlib.Path(self._cache_dir) cache_path.mkdir(mode=self._BUILD_DIR_PERMISSIONS, parents=True, exist_ok=True) self._cache_dir = str(cache_path.resolve()) if self._use_container: self._container_manager = ContainerManager( docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image) return self
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])
def __enter__(self) -> "BuildContext": self._stacks, remote_stack_full_paths = SamLocalStackProvider.get_stacks( self._template_file, parameter_overrides=self._parameter_overrides) if remote_stack_full_paths: LOG.warning( "Below nested stacks(s) specify non-local URL(s), which are unsupported:\n%s\n" "Skipping building resources inside these nested stacks.", "\n".join([ f"- {full_path}" for full_path in remote_stack_full_paths ]), ) # Note(xinhol): self._use_raw_codeuri is added temporarily to fix issue #2717 # when base_dir is provided, codeuri should not be resolved based on template file path. # we will refactor to make all path resolution inside providers intead of in multiple places self._function_provider = SamFunctionProvider(self.stacks, self._use_raw_codeuri) self._layer_provider = SamLayerProvider(self.stacks, self._use_raw_codeuri) 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._cached: cache_path = pathlib.Path(self._cache_dir) cache_path.mkdir(mode=self._BUILD_DIR_PERMISSIONS, parents=True, exist_ok=True) self._cache_dir = str(cache_path.resolve()) if self._use_container: self._container_manager = ContainerManager( docker_network_id=self._docker_network, skip_pull_image=self._skip_pull_image) return self
class BuildContext: # Build directories need not be world writable. # This is usually a optimal permission for directories _BUILD_DIR_PERMISSIONS = 0o755 def __init__( self, resource_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._resource_identifier = resource_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._layer_provider = None self._template_dict = None self._app_builder = None self._container_manager = None def __enter__(self): self._template_dict = get_template_data(self._template_file) self._function_provider = SamFunctionProvider( self._template_dict, self._parameter_overrides) self._layer_provider = SamLayerProvider(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 layer_provider(self): return self._layer_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 resources_to_build(self): """ Function return resources that should be build by current build command. This function considers Lambda Functions and Layers with build method as buildable resources. Returns ------- ResourcesToBuildCollector """ result = ResourcesToBuildCollector() if self._resource_identifier: self._collect_single_function_and_dependent_layers( self._resource_identifier, result) self._collect_single_buildable_layer(self._resource_identifier, result) if not result.functions and not result.layers: all_resources = [ f.name for f in self._function_provider.get_all() ] all_resources.extend( [l.name for l in self._layer_provider.get_all()]) available_resource_message = f"{self._resource_identifier} not found. Possible options in your " \ f"template: {all_resources}" LOG.info(available_resource_message) raise ResourceNotFound( f"Unable to find a function or layer with name '{self._resource_identifier}'" ) return result result.add_functions(self._function_provider.get_all()) result.add_layers([ l for l in self._layer_provider.get_all() if l.build_method is not None ]) return result @property def is_building_specific_resource(self): """ Whether customer requested to build a specific resource alone in isolation, by specifying function_identifier to the build command. Ex: sam build MyServerlessFunction :return: True if user requested to build specific resource, False otherwise """ return bool(self._resource_identifier) def _collect_single_function_and_dependent_layers(self, resource_identifier, resource_collector): """ Populate resource_collector with function with provided identifier and all layers that function need to be build in resource_collector Parameters ---------- resource_collector: Collector that will be populated with resources. Returns ------- ResourcesToBuildCollector """ function = self._function_provider.get(resource_identifier) if not function: # No function found return resource_collector.add_function(function) resource_collector.add_layers( [l for l in function.layers if l.build_method is not None]) def _collect_single_buildable_layer(self, resource_identifier, resource_collector): """ Populate resource_collector with layer with provided identifier. Parameters ---------- resource_collector Returns ------- """ layer = self._layer_provider.get(resource_identifier) if not layer: # No layer found return if layer and layer.build_method is None: LOG.error("Layer %s is missing BuildMethod Metadata.", self._function_provider) raise MissingBuildMethodException( f"Build method missing in layer {resource_identifier}.") resource_collector.add_layer(layer)
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 setUp(self): self.parameter_overrides = {} self.provider = SamLayerProvider(self.TEMPLATE, parameter_overrides=self.parameter_overrides)