From 47ff20c1793a1f738b75f424f65f4ac1bce94e44 Mon Sep 17 00:00:00 2001 From: Chris/0 Date: Wed, 10 Apr 2024 11:26:20 -0400 Subject: [PATCH] feat: sam Commands Understand Local File Paths for `ImageUri` As a summary, sam has learned to load an an image from a local archive before proceeding with the `build`, `package`, and `deploy` commands. When running `sam build` with an `ImageUri` pointing to a local file, sam will load that archive into an image, then write the ID of the image to the `ImageUri` property of the built template. ID works the same as a tag for the Docker API, so business continues as usual from here. The reason behind writing ID is that a loaded image could be associated with multiple tags, and selecting one arbtrarily leads to difficulties in the deploy command. The package and deploy commands have three kinds of value for `ImageUri` to consider. First, a value of the form `{repo}:{tag}`. This functions as it always has. Second, an image ID (in the form `sha256:{digest}`) which is probably the output of `sam build`. In this case, the tag translation uses the name of the as its input. Otherwise, they'd all end up with names starting with "sha256". Last, a local file. In this case, it proceeds as it does in the build command: Load the archive into an image first, then pass the resource name into tag translation. See: #6909 --- samcli/lib/build/app_builder.py | 18 +++++ samcli/lib/build/build_graph.py | 4 + samcli/lib/build/build_strategy.py | 5 +- samcli/lib/package/ecr_uploader.py | 21 ++++- samcli/lib/providers/provider.py | 7 +- samcli/lib/providers/sam_function_provider.py | 2 +- samcli/local/docker/lambda_container.py | 6 +- samcli/local/lambdafn/config.py | 3 +- .../commands/buildcmd/test_build_context.py | 4 +- .../unit/lib/build_module/test_app_builder.py | 77 +++++++++++++++---- .../unit/lib/build_module/test_build_graph.py | 76 ++++++++++++------ .../lib/build_module/test_build_strategy.py | 20 +++-- 12 files changed, 190 insertions(+), 53 deletions(-) diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index d4fa45ac271..fd04715cda5 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -7,6 +7,7 @@ import logging import os import pathlib +from pathlib import Path from typing import Dict, List, NamedTuple, Optional, cast import docker @@ -250,6 +251,7 @@ def _get_build_graph( function_build_details = FunctionBuildDefinition( function.runtime, function.codeuri, + function.imageuri, function.packagetype, function.architecture, function.metadata, @@ -460,6 +462,16 @@ def _stream_lambda_image_build_logs(self, build_logs: List[Dict[str, str]], func except LogStreamError as ex: raise DockerBuildFailed(msg=f"{function_name} failed to build: {str(ex)}") from ex + def _load_lambda_image(self, image_archive_path: str) -> str: + with open(image_archive_path, mode="rb") as image_archive: + try: + [image, *rest] = self._docker_client.images.load(image_archive) + if len(rest) != 0: + raise DockerBuildFailed("Archive must represent a single image") + return f"{image.id}" + except docker.errors.APIError as ex: + raise DockerBuildFailed(msg=str(ex)) from ex + def _build_layer( self, layer_name: str, @@ -599,6 +611,7 @@ def _build_function( # pylint: disable=R1710 self, function_name: str, codeuri: str, + imageuri: Optional[str], packagetype: str, runtime: str, architecture: str, @@ -619,6 +632,9 @@ def _build_function( # pylint: disable=R1710 Name or LogicalId of the function codeuri : str Path to where the code lives + imageuri : str + Location of the Lambda Image which is of the form {image}:{tag}, sha256:{digest}, + or a path to a local archive packagetype : str The package type, 'Zip' or 'Image', see samcli/lib/utils/packagetype.py runtime : str @@ -646,6 +662,8 @@ def _build_function( # pylint: disable=R1710 Path to the location where built artifacts are available """ if packagetype == IMAGE: + if imageuri and Path(imageuri).is_file(): # something exists at this path and what exists is a file + return self._load_lambda_image(imageuri) # should be an image archive – load it instead of building it # pylint: disable=fixme # FIXME: _build_lambda_image assumes metadata is not None, we need to throw an exception here return self._build_lambda_image( diff --git a/samcli/lib/build/build_graph.py b/samcli/lib/build/build_graph.py index a8121dd3dc9..78085ad4d35 100644 --- a/samcli/lib/build/build_graph.py +++ b/samcli/lib/build/build_graph.py @@ -107,6 +107,7 @@ def _toml_table_to_function_build_definition(uuid: str, toml_table: tomlkit.api. function_build_definition = FunctionBuildDefinition( toml_table.get(RUNTIME_FIELD), toml_table.get(CODE_URI_FIELD), + None, toml_table.get(PACKAGETYPE_FIELD, ZIP), toml_table.get(ARCHITECTURE_FIELD, X86_64), dict(toml_table.get(METADATA_FIELD, {})), @@ -584,6 +585,7 @@ def __init__( self, runtime: Optional[str], codeuri: Optional[str], + imageuri: Optional[str], packagetype: str, architecture: str, metadata: Optional[Dict], @@ -595,6 +597,7 @@ def __init__( super().__init__(source_hash, manifest_hash, env_vars, architecture) self.runtime = runtime self.codeuri = codeuri + self.imageuri = imageuri self.packagetype = packagetype self.handler = handler @@ -688,6 +691,7 @@ def __eq__(self, other: Any) -> bool: return ( self.runtime == other.runtime and self.codeuri == other.codeuri + and self.imageuri == other.imageuri and self.packagetype == other.packagetype and self.metadata == other.metadata and self.env_vars == other.env_vars diff --git a/samcli/lib/build/build_strategy.py b/samcli/lib/build/build_strategy.py index b1317e9311b..dbbf03a64bb 100644 --- a/samcli/lib/build/build_strategy.py +++ b/samcli/lib/build/build_strategy.py @@ -128,7 +128,9 @@ def __init__( self, build_graph: BuildGraph, build_dir: str, - build_function: Callable[[str, str, str, str, str, Optional[str], str, dict, dict, Optional[str], bool], str], + build_function: Callable[ + [str, str, Optional[str], str, str, str, Optional[str], str, dict, dict, Optional[str], bool], str + ], build_layer: Callable[[str, str, str, List[str], str, str, dict, Optional[str], bool, Optional[Dict]], str], cached: bool = False, ) -> None: @@ -166,6 +168,7 @@ def build_single_function_definition(self, build_definition: FunctionBuildDefini result = self._build_function( build_definition.get_function_name(), build_definition.codeuri, # type: ignore + build_definition.imageuri, build_definition.packagetype, build_definition.runtime, # type: ignore build_definition.architecture, diff --git a/samcli/lib/package/ecr_uploader.py b/samcli/lib/package/ecr_uploader.py index 6414ccf071b..2205733d066 100644 --- a/samcli/lib/package/ecr_uploader.py +++ b/samcli/lib/package/ecr_uploader.py @@ -5,6 +5,7 @@ import base64 import logging from io import StringIO +from pathlib import Path from typing import Dict import botocore @@ -76,10 +77,26 @@ def upload(self, image, resource_name): if not self.login_session_active: self.login() self.login_session_active = True + + # cosborn: We sometimes use the resource_name as the `image` parameter to `tag_translation`. + # This is because these two cases (directly from an archive or by ID) are effectively anonymous, + # so the best identifier we've been given is the resource name. try: - docker_img = self.docker_client.images.get(image) + if Path(image).is_file(): + with open(image, mode="rb") as image_archive: + [docker_img, *rest] = self.docker_client.images.load(image_archive) + if len(rest) != 0: + raise DockerPushFailedError("Archive must represent a single image") + _tag = tag_translation(resource_name, docker_image_id=docker_img.id, gen_tag=self.tag) + else: + # If it's not a file, it's gotta be a {repo}:{tag} or a sha256:{digest} + docker_img = self.docker_client.images.get(image) + _tag = tag_translation( + resource_name if image == docker_img.id else image, + docker_image_id=docker_img.id, + gen_tag=self.tag, + ) - _tag = tag_translation(image, docker_image_id=docker_img.id, gen_tag=self.tag) repository = ( self.ecr_repo if not self.ecr_repo_multi or not isinstance(self.ecr_repo_multi, dict) diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index d14f3fa4b0a..e7d5aa1e836 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -9,6 +9,7 @@ import posixpath from collections import namedtuple from enum import Enum +from pathlib import Path from typing import Any, Dict, Iterator, List, NamedTuple, Optional, Set, Union, cast from samcli.commands.local.cli_common.user_exceptions import ( @@ -953,6 +954,7 @@ def get_function_build_info( packagetype: str, inlinecode: Optional[str], codeuri: Optional[str], + imageuri: Optional[str], metadata: Optional[Dict], ) -> FunctionBuildInfo: """ @@ -974,8 +976,9 @@ def get_function_build_info( metadata = metadata or {} dockerfile = cast(str, metadata.get("Dockerfile", "")) docker_context = cast(str, metadata.get("DockerContext", "")) - - if not dockerfile or not docker_context: + buildable = dockerfile and docker_context + loadable = imageuri and Path(imageuri).is_file() + if not buildable and not loadable: LOG.debug( "Skip Building %s function, as it is missing either Dockerfile or DockerContext " "metadata properties.", diff --git a/samcli/lib/providers/sam_function_provider.py b/samcli/lib/providers/sam_function_provider.py index da0c64eaca3..09914a56ee6 100644 --- a/samcli/lib/providers/sam_function_provider.py +++ b/samcli/lib/providers/sam_function_provider.py @@ -451,7 +451,7 @@ def _build_function_configuration( raise MissingFunctionHandlerException(f"Could not find handler for function: {name}") function_build_info = get_function_build_info( - get_full_path(stack.stack_path, function_id), package_type, inlinecode, codeuri, metadata + get_full_path(stack.stack_path, function_id), package_type, inlinecode, codeuri, imageuri, metadata ) return Function( diff --git a/samcli/local/docker/lambda_container.py b/samcli/local/docker/lambda_container.py index 609d5bfbb3f..d59b6709201 100644 --- a/samcli/local/docker/lambda_container.py +++ b/samcli/local/docker/lambda_container.py @@ -65,7 +65,8 @@ def __init__( runtime str Name of the Lambda runtime imageuri str - Name of the Lambda Image which is of the form {image}:{tag} + Location of the Lambda Image which is of the form {image}:{tag}, sha256:{digest}, + or a path to a local archive handler str Handler of the function to run packagetype str @@ -240,7 +241,8 @@ def _get_image( packagetype : str Package type for the lambda function which is either zip or image. image : str - Name of the Lambda Image which is of the form {image}:{tag} + Location of the Lambda Image which is of the form {image}:{tag}, sha256:{digest}, + or a path to a local archive layers : List[str] List of layers architecture : str diff --git a/samcli/local/lambdafn/config.py b/samcli/local/lambdafn/config.py index cadbfbd9d3e..1d940aed2df 100644 --- a/samcli/local/lambdafn/config.py +++ b/samcli/local/lambdafn/config.py @@ -46,7 +46,8 @@ def __init__( handler : str Handler method imageuri : str - Name of the Lambda Image which is of the form {image}:{tag} + Location of the Lambda Image which is of the form {image}:{tag}, sha256:{digest}, + or a path to a local archive imageconfig : str Image configuration which can be used set to entrypoint, command and working dir for the container. packagetype : str diff --git a/tests/unit/commands/buildcmd/test_build_context.py b/tests/unit/commands/buildcmd/test_build_context.py index 5d48139d4b6..a69b83bdaca 100644 --- a/tests/unit/commands/buildcmd/test_build_context.py +++ b/tests/unit/commands/buildcmd/test_build_context.py @@ -76,7 +76,9 @@ def get_function( function_url_config=None, stack_path="", runtime_management_config=None, - function_build_info=get_function_build_info("stack/function", packagetype, inlinecode, codeuri, metadata), + function_build_info=get_function_build_info( + "stack/function", packagetype, inlinecode, codeuri, imageuri, metadata + ), ) diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 92c584ba306..dc311813032 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -56,11 +56,12 @@ def setUp(self): self.imageFunc1.get_build_dir = Mock() self.imageFunc1.inlinecode = None self.imageFunc1.architectures = [X86_64] + self.imageFunc1.packagetype = IMAGE + self.imageFunc1.imageuri = "imageuri" self.layer1 = Mock() self.layer2 = Mock() - self.imageFunc1.packagetype = IMAGE self.layer1.build_method = "build_method" self.layer1.name = "layer_name1" self.layer1.full_path = os.path.join("StackJ", "layer_name1") @@ -131,6 +132,7 @@ def build_layer_return( call( self.func1.name, self.func1.codeuri, + ANY, ZIP, self.func1.runtime, self.func1.architecture, @@ -144,6 +146,7 @@ def build_layer_return( call( self.func2.name, self.func2.codeuri, + ANY, ZIP, self.func2.runtime, self.func2.architecture, @@ -157,6 +160,7 @@ def build_layer_return( call( self.imageFunc1.name, self.imageFunc1.codeuri, + self.imageFunc1.imageuri, IMAGE, self.imageFunc1.runtime, self.imageFunc1.architecture, @@ -203,7 +207,7 @@ def build_layer_return( @patch("samcli.lib.build.build_graph.BuildGraph._write") def test_should_use_function_or_layer_get_build_dir_to_determine_artifact_dir(self, persist_mock): def get_func_call_with_artifact_dir(artifact_dir): - return call(ANY, ANY, ANY, ANY, ANY, ANY, artifact_dir, ANY, ANY, ANY, True) + return call(ANY, ANY, ANY, ANY, ANY, ANY, ANY, artifact_dir, ANY, ANY, ANY, True) def get_layer_call_with_artifact_dir(artifact_dir): return call(ANY, ANY, ANY, ANY, ANY, artifact_dir, ANY, ANY, True, ANY) @@ -296,6 +300,7 @@ def test_should_run_build_for_only_unique_builds(self, persist_mock, read_mock, call( function1_1.name, function1_1.codeuri, + ANY, ZIP, function1_1.runtime, function1_1.architectures[0], @@ -309,6 +314,7 @@ def test_should_run_build_for_only_unique_builds(self, persist_mock, read_mock, call( function2.name, function2.codeuri, + ANY, ZIP, function2.runtime, function1_1.architectures[0], @@ -480,6 +486,7 @@ def test_deprecated_runtimes(self, runtime): self.builder._build_function( function_name="function_name", codeuri="code_uri", + imageuri=None, packagetype=ZIP, runtime=runtime, architecture="architecture", @@ -527,7 +534,18 @@ def test_must_not_use_dep_layer_for_non_cached(self): builder.build() builder._build_function.assert_called_with( - "name", "codeuri", ZIP, "runtime", X86_64, "handler", str(Path("builddir/name")), {}, {}, None, True + "name", + "codeuri", + "imageuri", + ZIP, + "runtime", + X86_64, + "handler", + str(Path("builddir/name")), + {}, + {}, + None, + True, ) @@ -1744,7 +1762,7 @@ def test_must_build_in_process(self, osutils_mock, get_workflow_config_mock): artifacts_dir = str(Path("/build/dir/function_full_path")) manifest_path = str(Path(os.path.join(code_dir, config_mock.manifest_name)).resolve()) - self.builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir) + self.builder._build_function(function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir) self.builder._build_function_in_process.assert_called_with( config_mock, @@ -1807,7 +1825,9 @@ def test_must_custom_build_function_with_working_dir_metadata_in_process( get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -1883,7 +1903,9 @@ def test_must_custom_build_function_with_custom_makefile_and_custom_project_root get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -1961,7 +1983,9 @@ def test_must_custom_build_function_with_all_metadata_sutom_paths_properties_in_ get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -2037,7 +2061,9 @@ def test_must_custom_build_function_with_only_context_path_metadata_in_process( get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -2112,7 +2138,9 @@ def test_must_custom_build_function_with_only_project_root_dir_metadata_in_proce get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -2183,7 +2211,9 @@ def test_must_custom_build_function_with_empty_metadata_in_process(self, osutils get_build_options = ApplicationBuilder._get_build_options ApplicationBuilder._get_build_options = get_build_options_mock builder._build_function_in_process = build_function_in_process_mock - builder._build_function(function_name, codeuri, ZIP, runtime, architecture, handler, artifacts_dir, metadata) + builder._build_function( + function_name, codeuri, None, ZIP, runtime, architecture, handler, artifacts_dir, metadata + ) ApplicationBuilder._get_build_options = get_build_options @@ -2237,6 +2267,7 @@ def test_must_build_in_process_with_metadata(self, osutils_mock, get_workflow_co self.builder._build_function( function_name, codeuri, + None, packagetype, runtime, architecture, @@ -2294,6 +2325,7 @@ def test_must_build_in_process_with_metadata_and_metadata_as_options( self.builder._build_function( function_name, codeuri, + None, packagetype, runtime, architecture, @@ -2344,7 +2376,9 @@ def test_must_build_in_container(self, osutils_mock, get_workflow_config_mock): # Settting the container manager will make us use the container self.builder._container_manager = Mock() - self.builder._build_function(function_name, codeuri, packagetype, runtime, architecture, handler, artifacts_dir) + self.builder._build_function( + function_name, codeuri, None, packagetype, runtime, architecture, handler, artifacts_dir + ) self.builder._build_function_on_container.assert_called_with( config_mock, @@ -2387,6 +2421,7 @@ def test_must_build_in_container_with_env_vars(self, osutils_mock, get_workflow_ self.builder._build_function( function_name, codeuri, + None, packagetype, runtime, architecture, @@ -2436,7 +2471,15 @@ def test_must_build_in_container_with_custom_specified_build_image(self, osutils self.builder._container_manager = Mock() self.builder._build_images = build_images self.builder._build_function( - function_name, codeuri, packagetype, runtime, architecture, handler, artifacts_dir, container_env_vars=None + function_name, + codeuri, + None, + packagetype, + runtime, + architecture, + handler, + artifacts_dir, + container_env_vars=None, ) self.builder._build_function_on_container.assert_called_with( @@ -2480,7 +2523,15 @@ def test_must_build_in_container_with_custom_default_build_image(self, osutils_m self.builder._container_manager = Mock() self.builder._build_images = build_images self.builder._build_function( - function_name, codeuri, packagetype, runtime, architecture, handler, artifacts_dir, container_env_vars=None + function_name, + codeuri, + None, + packagetype, + runtime, + architecture, + handler, + artifacts_dir, + container_env_vars=None, ) self.builder._build_function_on_container.assert_called_with( diff --git a/tests/unit/lib/build_module/test_build_graph.py b/tests/unit/lib/build_module/test_build_graph.py index 06f01fe35ea..898e7a965dd 100644 --- a/tests/unit/lib/build_module/test_build_graph.py +++ b/tests/unit/lib/build_module/test_build_graph.py @@ -111,6 +111,7 @@ def test_function_build_definition_to_toml_table(self): build_definition = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, X86_64, {"key": "value"}, @@ -209,7 +210,7 @@ def test_toml_table_to_layer_build_definition(self): self.assertEqual(build_definition.architecture, toml_table[ARCHITECTURE_FIELD]) def test_minimal_function_build_definition_to_toml_table(self): - build_definition = FunctionBuildDefinition("runtime", "codeuri", ZIP, X86_64, {"key": "value"}, "handler") + build_definition = FunctionBuildDefinition("runtime", "codeuri", None, ZIP, X86_64, {"key": "value"}, "handler") build_definition.add_function(generate_function()) toml_table = _function_build_definition_to_toml_table(build_definition) @@ -354,6 +355,7 @@ def test_should_instantiate_first_time_and_update(self): function_build_definition1 = FunctionBuildDefinition( TestBuildGraph.RUNTIME, TestBuildGraph.CODEURI, + None, TestBuildGraph.ZIP, TestBuildGraph.ARCHITECTURE_FIELD, TestBuildGraph.METADATA, @@ -442,6 +444,7 @@ def test_functions_should_be_added_existing_build_graph(self): build_definition1 = FunctionBuildDefinition( TestBuildGraph.RUNTIME, TestBuildGraph.CODEURI, + None, TestBuildGraph.ZIP, TestBuildGraph.ARCHITECTURE_FIELD, TestBuildGraph.METADATA, @@ -466,6 +469,7 @@ def test_functions_should_be_added_existing_build_graph(self): build_definition2 = FunctionBuildDefinition( "another_runtime", "another_codeuri", + None, TestBuildGraph.ZIP, ARM64, None, @@ -565,6 +569,7 @@ def test_compare_hash_changes_should_succeed(self): build_definition = FunctionBuildDefinition( TestBuildGraph.RUNTIME, TestBuildGraph.CODEURI, + None, TestBuildGraph.ZIP, TestBuildGraph.ARCHITECTURE_FIELD, TestBuildGraph.METADATA, @@ -576,6 +581,7 @@ def test_compare_hash_changes_should_succeed(self): updated_definition = FunctionBuildDefinition( TestBuildGraph.RUNTIME, TestBuildGraph.CODEURI, + None, TestBuildGraph.ZIP, TestBuildGraph.ARCHITECTURE_FIELD, TestBuildGraph.METADATA, @@ -628,10 +634,10 @@ def test_compare_hash_changes_should_preserve_download_dependencies( self, old_manifest, new_manifest, download_dependencies ): updated_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, {}, "app.handler", manifest_hash=old_manifest + "runtime", "codeuri", None, ZIP, X86_64, {}, "app.handler", manifest_hash=old_manifest ) existing_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, {}, "app.handler", manifest_hash=new_manifest + "runtime", "codeuri", None, ZIP, X86_64, {}, "app.handler", manifest_hash=new_manifest ) BuildGraph._compare_hash_changes([updated_definition], [existing_definition]) self.assertEqual(existing_definition.download_dependencies, download_dependencies) @@ -688,7 +694,16 @@ def test_get_function_build_definition_with_logical_id(self): class TestBuildDefinition(TestCase): def test_single_function_should_return_function_and_handler_name(self): build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, {}, "handler", "source_hash", "manifest_hash", {"env_vars": "value"} + "runtime", + "codeuri", + None, + ZIP, + X86_64, + {}, + "handler", + "source_hash", + "manifest_hash", + {"env_vars": "value"}, ) build_definition.add_function(generate_function()) self.assertEqual(build_definition.get_handler_name(), "handler") @@ -696,7 +711,16 @@ def test_single_function_should_return_function_and_handler_name(self): def test_no_function_should_raise_exception(self): build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, {}, "handler", "source_hash", "manifest_hash", {"env_vars": "value"} + "runtime", + "codeuri", + None, + ZIP, + X86_64, + {}, + "handler", + "source_hash", + "manifest_hash", + {"env_vars": "value"}, ) self.assertRaises(InvalidBuildGraphException, build_definition.get_handler_name) @@ -704,10 +728,10 @@ def test_no_function_should_raise_exception(self): def test_same_runtime_codeuri_metadata_should_reflect_as_same_object(self): build_definition1 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {"key": "value"}, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, {"key": "value"}, "handler", "source_hash", "manifest_hash" ) build_definition2 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {"key": "value"}, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, {"key": "value"}, "handler", "source_hash", "manifest_hash" ) self.assertEqual(build_definition1, build_definition2) @@ -716,6 +740,7 @@ def test_skip_sam_related_metadata_should_reflect_as_same_object(self): build_definition1 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, ARM64, {"key": "value", "SamResourceId": "resourceId1", "SamNormalized": True}, @@ -726,6 +751,7 @@ def test_skip_sam_related_metadata_should_reflect_as_same_object(self): build_definition2 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, ARM64, {"key": "value", "SamResourceId": "resourceId2", "SamNormalized": True}, @@ -740,6 +766,7 @@ def test_same_env_vars_reflect_as_same_object(self): build_definition1 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, X86_64, {"key": "value"}, @@ -751,6 +778,7 @@ def test_same_env_vars_reflect_as_same_object(self): build_definition2 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, X86_64, {"key": "value"}, @@ -810,17 +838,17 @@ def test_same_env_vars_reflect_as_same_object(self): def test_different_runtime_codeuri_metadata_should_not_reflect_as_same_object( self, runtime1, codeuri1, metadata1, source_hash_1, runtime2, codeuri2, metadata2, source_hash_2 ): - build_definition1 = FunctionBuildDefinition(runtime1, codeuri1, ZIP, ARM64, metadata1, source_hash_1) - build_definition2 = FunctionBuildDefinition(runtime2, codeuri2, ZIP, ARM64, metadata2, source_hash_2) + build_definition1 = FunctionBuildDefinition(runtime1, codeuri1, None, ZIP, ARM64, metadata1, source_hash_1) + build_definition2 = FunctionBuildDefinition(runtime2, codeuri2, None, ZIP, ARM64, metadata2, source_hash_2) self.assertNotEqual(build_definition1, build_definition2) def test_different_architecture_should_not_reflect_as_same_object(self): build_definition1 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, {"key": "value"}, "handler", "source_md5", {"env_vars": "value"} + "runtime", "codeuri", None, ZIP, X86_64, {"key": "value"}, "handler", "source_md5", {"env_vars": "value"} ) build_definition2 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {"key": "value"}, "handler", "source_md5", {"env_vars": "value"} + "runtime", "codeuri", None, ZIP, ARM64, {"key": "value"}, "handler", "source_md5", {"env_vars": "value"} ) self.assertNotEqual(build_definition1, build_definition2) @@ -829,6 +857,7 @@ def test_different_env_vars_should_not_reflect_as_same_object(self): build_definition1 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, ARM64, {"key": "value"}, @@ -840,6 +869,7 @@ def test_different_env_vars_should_not_reflect_as_same_object(self): build_definition2 = FunctionBuildDefinition( "runtime", "codeuri", + None, ZIP, ARM64, {"key": "value"}, @@ -853,13 +883,13 @@ def test_different_env_vars_should_not_reflect_as_same_object(self): def test_euqality_with_another_object(self): build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, X86_64, None, "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, X86_64, None, "source_hash", "manifest_hash" ) self.assertNotEqual(build_definition, {}) def test_str_representation(self): build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, None, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, None, "handler", "source_hash", "manifest_hash" ) self.assertEqual( str(build_definition), @@ -870,13 +900,13 @@ def test_esbuild_definitions_equal_objects_independent_build_method(self): build_graph = BuildGraph("build/path") metadata = {"BuildMethod": "esbuild"} build_definition1 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" ) function1 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler-1" ) build_definition2 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, metadata, "app.handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, metadata, "app.handler", "source_hash", "manifest_hash" ) function2 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler-2" @@ -895,13 +925,13 @@ def test_independent_build_definitions_equal_objects_one_esbuild_build_method(se build_graph = BuildGraph("build/path") metadata = {"BuildMethod": "esbuild"} build_definition1 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" ) function1 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler-1" ) build_definition2 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" ) function2 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata={}, handler="handler-2" @@ -920,13 +950,13 @@ def test_two_esbuild_methods_same_handler(self): build_graph = BuildGraph("build/path") metadata = {"BuildMethod": "esbuild"} build_definition1 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" ) function1 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler" ) build_definition2 = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" ) function2 = generate_function( runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, metadata={}, handler="handler" @@ -946,7 +976,7 @@ def test_build_folder_with_multiple_functions(self, build_improvements_22_enable patched_is_experimental.return_value = build_improvements_22_enabled build_graph = BuildGraph("build/path") build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" ) function1 = generate_function(runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, handler="handler") function2 = generate_function(runtime=TestBuildGraph.RUNTIME, codeuri=TestBuildGraph.CODEURI, handler="handler") @@ -965,7 +995,7 @@ def test_build_folder_with_multiple_functions(self, build_improvements_22_enable def test_deepcopy_build_definition(self): build_definition = FunctionBuildDefinition( - "runtime", "codeuri", ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" + "runtime", "codeuri", None, ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" ) function1 = generate_function(runtime="runtime", codeuri="codeuri", handler="handler") function2 = generate_function(runtime="runtime", codeuri="codeuri", handler="handler") @@ -981,13 +1011,13 @@ def test_go_runtime_different_handlers_are_not_equal(self): build_graph = BuildGraph("build/path") metadata = {} build_definition1 = FunctionBuildDefinition( - "go1.x", "codeuri", ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" + "go1.x", "codeuri", None, ZIP, ARM64, metadata, "handler", "source_hash", "manifest_hash" ) function1 = generate_function( runtime="go1.x", codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler" ) build_definition2 = FunctionBuildDefinition( - "go1.x", "codeuri", ZIP, ARM64, metadata, "handler.new", "source_hash", "manifest_hash" + "go1.x", "codeuri", None, ZIP, ARM64, metadata, "handler.new", "source_hash", "manifest_hash" ) function2 = generate_function( runtime="go1.x", codeuri=TestBuildGraph.CODEURI, metadata=metadata, handler="handler.new" diff --git a/tests/unit/lib/build_module/test_build_strategy.py b/tests/unit/lib/build_module/test_build_strategy.py index 27ea179a778..07330b8bf81 100644 --- a/tests/unit/lib/build_module/test_build_strategy.py +++ b/tests/unit/lib/build_module/test_build_strategy.py @@ -44,8 +44,12 @@ def setUp(self): self.function2.get_build_dir = Mock() self.function2.full_path = "function2" - self.function_build_definition1 = FunctionBuildDefinition("runtime", "codeuri", ZIP, X86_64, {}, "handler") - self.function_build_definition2 = FunctionBuildDefinition("runtime2", "codeuri", ZIP, X86_64, {}, "handler") + self.function_build_definition1 = FunctionBuildDefinition( + "runtime", "codeuri", None, ZIP, X86_64, {}, "handler" + ) + self.function_build_definition2 = FunctionBuildDefinition( + "runtime2", "codeuri", None, ZIP, X86_64, {}, "handler" + ) self.function_build_definition1.add_function(self.function1_1) self.function_build_definition1.add_function(self.function1_2) @@ -218,6 +222,7 @@ def test_build_layers_and_functions(self, mock_copy_tree): call( self.function_build_definition1.get_function_name(), self.function_build_definition1.codeuri, + self.function_build_definition1.imageuri, ZIP, self.function_build_definition1.runtime, self.function_build_definition1.architecture, @@ -231,6 +236,7 @@ def test_build_layers_and_functions(self, mock_copy_tree): call( self.function_build_definition2.get_function_name(), self.function_build_definition2.codeuri, + self.function_build_definition2.imageuri, ZIP, self.function_build_definition2.runtime, self.function_build_definition2.architecture, @@ -323,7 +329,7 @@ def test_build_single_function_definition_image_functions_with_same_metadata(sel function2.full_path = "Function2" function2.packagetype = IMAGE build_definition = FunctionBuildDefinition( - "3.12", "codeuri", IMAGE, X86_64, {}, "handler", env_vars={"FOO": "BAR"} + "3.12", "codeuri", "imageuri", IMAGE, X86_64, {}, "handler", env_vars={"FOO": "BAR"} ) # since they have the same metadata, they are put into the same build_definition. build_definition.functions = [function1, function2] @@ -678,7 +684,7 @@ def test_assert_incremental_build_function(self, patched_manifest_hash, patched_ self.build_strategy.build() self.build_function.assert_called_with( - ANY, ANY, ANY, ANY, ANY, ANY, ANY, ANY, ANY, dependency_dir, download_dependencies + ANY, ANY, ANY, ANY, ANY, ANY, ANY, ANY, ANY, ANY, dependency_dir, download_dependencies ) @parameterized.expand( @@ -739,7 +745,7 @@ def setUp(self) -> None: ] ) def test_will_call_incremental_build_strategy(self, mocked_read, mocked_write, runtime): - build_definition = FunctionBuildDefinition(runtime, "codeuri", "packate_type", X86_64, {}, "handler") + build_definition = FunctionBuildDefinition(runtime, "codeuri", None, "packate_type", X86_64, {}, "handler") self.build_graph.put_function_build_definition(build_definition, Mock(full_path="function_full_path")) with patch.object( self.build_strategy, "_incremental_build_strategy" @@ -759,7 +765,7 @@ def test_will_call_incremental_build_strategy(self, mocked_read, mocked_write, r ] ) def test_will_call_cached_build_strategy(self, mocked_read, mocked_write, runtime): - build_definition = FunctionBuildDefinition(runtime, "codeuri", "packate_type", X86_64, {}, "handler") + build_definition = FunctionBuildDefinition(runtime, "codeuri", None, "packate_type", X86_64, {}, "handler") self.build_graph.put_function_build_definition(build_definition, Mock(full_path="function_full_path")) with patch.object( self.build_strategy, "_incremental_build_strategy" @@ -833,7 +839,7 @@ def test_wrapper_with_or_without_container(self, mocked_read, mocked_write, runt use_container, ) - build_definition = FunctionBuildDefinition(runtime, "codeuri", "packate_type", X86_64, {}, "handler") + build_definition = FunctionBuildDefinition(runtime, "codeuri", None, "packate_type", X86_64, {}, "handler") self.build_graph.put_function_build_definition(build_definition, Mock(full_path="function_full_path")) with patch.object( build_strategy, "_incremental_build_strategy"