def test_must_init_class(self, get_container_dirs_mock, get_entrypoint_mock, get_image_mock, make_request_mock): request = make_request_mock.return_value = "somerequest" entry = get_entrypoint_mock.return_value = "entrypoint" image = get_image_mock.return_value = "imagename" container_dirs = get_container_dirs_mock.return_value = { "source_dir": "/mysource", "manifest_dir": "/mymanifest", "artifacts_dir": "/myartifacts", "scratch_dir": "/myscratch", } container = LambdaBuildContainer( "protocol", "language", "dependency", "application", "/foo/source", "/bar/manifest.txt", "runtime", optimizations="optimizations", options="options", log_level="log-level", mode="mode", ) self.assertEqual(container.image, image) self.assertEqual(container.executable_name, "lambda-builders") self.assertEqual(container._entrypoint, entry) self.assertEqual(container._cmd, []) self.assertEqual(container._working_dir, container_dirs["source_dir"]) self.assertEqual(container._host_dir, str(pathlib.Path("/foo/source").resolve())) self.assertEqual(container._env_vars, {"LAMBDA_BUILDERS_LOG_LEVEL": "log-level"}) self.assertEqual( container._additional_volumes, { str(pathlib.Path("/bar").resolve()): { "bind": container_dirs["manifest_dir"], "mode": "ro" } }, ) self.assertEqual(container._exposed_ports, None) self.assertEqual(container._memory_limit_mb, None) self.assertEqual(container._network_id, None) self.assertEqual(container._container_opts, None) make_request_mock.assert_called_once() get_entrypoint_mock.assert_called_once_with(request) get_image_mock.assert_called_once_with("runtime") get_container_dirs_mock.assert_called_once_with( str(pathlib.Path("/foo/source").resolve()), str(pathlib.Path("/bar").resolve()))
def test_must_skip_on_empty_input(self): input = None mapping = {"/known/path": "/first"} expected = None result = LambdaBuildContainer._convert_to_container_dirs(input, mapping) self.assertEqual(result, expected)
def test_must_skip_unknown_paths(self): input = ["/known/path", "/unknown/path"] mapping = {"/known/path": "/first"} expected = ["/first", "/unknown/path"] result = LambdaBuildContainer._convert_to_container_dirs(input, mapping) self.assertEqual(result, expected)
def test_must_work_on_abs_and_relative_paths(self): input = [".", "../foo", "/some/abs/path"] mapping = {str(pathlib.Path(".").resolve()): "/first", "../foo": "/second", "/some/abs/path": "/third"} expected = ["/first", "/second", "/third"] result = LambdaBuildContainer._convert_to_container_dirs(input, mapping) self.assertEqual(result, expected)
def _build_function_on_container( self, # pylint: disable=too-many-locals config, source_dir, artifacts_dir, scratch_dir, manifest_path, runtime): # If we are printing debug logs in SAM CLI, the builder library should also print debug logs log_level = LOG.getEffectiveLevel() container = LambdaBuildContainer(lambda_builders_protocol_version, config.language, config.dependency_manager, config.application_framework, source_dir, manifest_path, runtime, log_level=log_level, optimizations=None, options=None) try: try: self._container_manager.run(container) except docker.errors.APIError as ex: if "executable file not found in $PATH" in str(ex): raise UnsupportedBuilderLibraryVersionError( container.image, "{} executable not found in container".format( container.executable_name)) # Container's output provides status of whether the build succeeded or failed # stdout contains the result of JSON-RPC call stdout_stream = io.BytesIO() # stderr contains logs printed by the builder. Stream it directly to terminal stderr_stream = osutils.stderr() container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream) stdout_data = stdout_stream.getvalue().decode('utf-8') LOG.debug("Build inside container returned response %s", stdout_data) response = self._parse_builder_response(stdout_data, container.image) # Request is successful. Now copy the artifacts back to the host LOG.debug( "Build inside container was successful. Copying artifacts from container to host" ) # "/." is a Docker thing that instructions the copy command to download contents of the folder only result_dir_in_container = response["result"]["artifacts_dir"] + "/." container.copy(result_dir_in_container, artifacts_dir) finally: self._container_manager.stop(container) LOG.debug("Build inside container succeeded") return artifacts_dir
def test_must_return_dirs(self): source_dir = "source" manifest_dir = "manifest" result = LambdaBuildContainer._get_container_dirs( source_dir, manifest_dir) self.assertEquals( result, { "source_dir": "/tmp/samcli/source", "manifest_dir": "/tmp/samcli/manifest", "artifacts_dir": "/tmp/samcli/artifacts", "scratch_dir": "/tmp/samcli/scratch", })
def test_must_make_request_object_string(self): container_dirs = { "source_dir": "source_dir", "artifacts_dir": "artifacts_dir", "scratch_dir": "scratch_dir", "manifest_dir": "manifest_dir", } result = LambdaBuildContainer._make_request( "protocol", "language", "dependency", "application", container_dirs, "manifest_file_name", "runtime", "optimizations", "options", "executable_search_paths", "mode", ) self.maxDiff = None # Print whole json diff self.assertEqual( json.loads(result), { "jsonschema": "2.0", "id": 1, "method": "LambdaBuilder.build", "params": { "__protocol_version": "protocol", "capability": { "language": "language", "dependency_manager": "dependency", "application_framework": "application", }, "source_dir": "source_dir", "artifacts_dir": "artifacts_dir", "scratch_dir": "scratch_dir", "manifest_path": "manifest_dir/manifest_file_name", "runtime": "runtime", "optimizations": "optimizations", "options": "options", "executable_search_paths": "executable_search_paths", "mode": "mode", }, }, )
def test_must_override_manifest_if_equal_to_source(self): source_dir = "/home/source" manifest_dir = "/home/source" result = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir) self.assertEqual( result, { # When source & manifest directories are the same, manifest_dir must be equal to source "source_dir": "/tmp/samcli/source", "manifest_dir": "/tmp/samcli/source", "artifacts_dir": "/tmp/samcli/artifacts", "scratch_dir": "/tmp/samcli/scratch", }, )
def test_must_get_entrypoint(self): self.assertEquals(["lambda-builders", "requestjson"], LambdaBuildContainer._get_entrypoint("requestjson"))
def test_must_get_image_name(self): self.assertEquals("lambci/lambda:build-myruntime", LambdaBuildContainer._get_image("myruntime"))
def test_must_get_image_name(self, runtime, expected_image_name): self.assertEqual(expected_image_name, LambdaBuildContainer._get_image(runtime))
def _build_function_on_container( self, # pylint: disable=too-many-locals config: CONFIG, source_dir: str, artifacts_dir: str, scratch_dir: str, manifest_path: str, runtime: str, options: Optional[Dict], container_env_vars: Optional[Dict] = None, ) -> str: # _build_function_on_container() is only called when self._container_manager if not None if not self._container_manager: raise RuntimeError( "_build_function_on_container() is called when self._container_manager is None." ) if not self._container_manager.is_docker_reachable: raise BuildInsideContainerError( "Docker is unreachable. Docker needs to be running to build inside a container." ) container_build_supported, reason = supports_build_in_container(config) if not container_build_supported: raise ContainerBuildNotSupported(reason) # If we are printing debug logs in SAM CLI, the builder library should also print debug logs log_level = LOG.getEffectiveLevel() container_env_vars = container_env_vars or {} container = LambdaBuildContainer( lambda_builders_protocol_version, config.language, config.dependency_manager, config.application_framework, source_dir, manifest_path, runtime, log_level=log_level, optimizations=None, options=options, executable_search_paths=config.executable_search_paths, mode=self._mode, env_vars=container_env_vars, ) try: try: self._container_manager.run(container) except docker.errors.APIError as ex: if "executable file not found in $PATH" in str(ex): raise UnsupportedBuilderLibraryVersionError( container.image, "{} executable not found in container".format( container.executable_name)) from ex # Container's output provides status of whether the build succeeded or failed # stdout contains the result of JSON-RPC call stdout_stream = io.BytesIO() # stderr contains logs printed by the builder. Stream it directly to terminal stderr_stream = osutils.stderr() container.wait_for_logs(stdout=stdout_stream, stderr=stderr_stream) stdout_data = stdout_stream.getvalue().decode("utf-8") LOG.debug("Build inside container returned response %s", stdout_data) response = self._parse_builder_response(stdout_data, container.image) # Request is successful. Now copy the artifacts back to the host LOG.debug( "Build inside container was successful. Copying artifacts from container to host" ) # "/." is a Docker thing that instructions the copy command to download contents of the folder only result_dir_in_container = response["result"]["artifacts_dir"] + "/." container.copy(result_dir_in_container, artifacts_dir) finally: self._container_manager.stop(container) LOG.debug("Build inside container succeeded") return artifacts_dir