From d20cb6760d2152ba7ee429c64df81a6b6fcf7b27 Mon Sep 17 00:00:00 2001 From: Sanath Kumar Ramesh Date: Thu, 28 Feb 2019 14:21:32 -0800 Subject: [PATCH] fix(build): use container directories in executable_search_paths when building in container (#1030) Fixes #1029 --- samcli/local/docker/lambda_build_container.py | 60 +++++++++++++++++++ .../docker/test_lambda_build_container.py | 46 ++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/samcli/local/docker/lambda_build_container.py b/samcli/local/docker/lambda_build_container.py index 9921123d9a..b7ce5ff60d 100644 --- a/samcli/local/docker/lambda_build_container.py +++ b/samcli/local/docker/lambda_build_container.py @@ -46,6 +46,18 @@ def __init__(self, # pylint: disable=too-many-locals container_dirs = LambdaBuildContainer._get_container_dirs(source_dir, manifest_dir) + # `executable_search_paths` are provided as a list of paths on the host file system that needs to passed to + # the builder. But these paths don't exist within the container. We use the following method to convert the + # host paths to container paths. But if a host path is NOT mounted within the container, we will simply ignore + # it. In essence, only when the path is already in the mounted path, can the path resolver within the + # container even find the executable. + executable_search_paths = LambdaBuildContainer._convert_to_container_dirs( + host_paths_to_convert=executable_search_paths, + host_to_container_path_mapping={ + source_dir: container_dirs["source_dir"], + manifest_dir: container_dirs["manifest_dir"] + }) + request_json = self._make_request(protocol_version, language, dependency_manager, @@ -163,6 +175,54 @@ def _get_container_dirs(source_dir, manifest_dir): return result + @staticmethod + def _convert_to_container_dirs(host_paths_to_convert, host_to_container_path_mapping): + """ + Use this method to convert a list of host paths to a list of equivalent paths within the container + where the given host path is mounted. This is necessary when SAM CLI needs to pass path information to + the Lambda Builder running within the container. + + If a host path is not mounted within the container, then this method simply passes the path to the result + without any changes. + + Ex: + [ "/home/foo", "/home/bar", "/home/not/mounted"] => ["/tmp/source", "/tmp/manifest", "/home/not/mounted"] + + Parameters + ---------- + host_paths_to_convert : list + List of paths in host that needs to be converted + + host_to_container_path_mapping : dict + Mapping of paths in host to the equivalent paths within the container + + Returns + ------- + list + Equivalent paths within the container + """ + + if not host_paths_to_convert: + # Nothing to do + return host_paths_to_convert + + # Make sure the key is absolute host path. Relative paths are tricky to work with because two different + # relative paths can point to the same directory ("../foo", "../../foo") + mapping = {str(pathlib.Path(p).resolve()): v for p, v in host_to_container_path_mapping.items()} + + result = [] + for original_path in host_paths_to_convert: + abspath = str(pathlib.Path(original_path).resolve()) + + if abspath in mapping: + result.append(mapping[abspath]) + else: + result.append(original_path) + LOG.debug("Cannot convert host path '%s' to its equivalent path within the container. " + "Host path is not mounted within the container", abspath) + + return result + @staticmethod def _get_image(runtime): return "{}:build-{}".format(LambdaBuildContainer._IMAGE_REPO_NAME, runtime) diff --git a/tests/unit/local/docker/test_lambda_build_container.py b/tests/unit/local/docker/test_lambda_build_container.py index a64ec8de05..8c5421b604 100644 --- a/tests/unit/local/docker/test_lambda_build_container.py +++ b/tests/unit/local/docker/test_lambda_build_container.py @@ -3,6 +3,11 @@ """ import json +try: + import pathlib +except ImportError: + import pathlib2 as pathlib + from unittest import TestCase from mock import patch @@ -157,3 +162,44 @@ class TestLambdaBuildContainer_get_entrypoint(TestCase): def test_must_get_entrypoint(self): self.assertEquals(["lambda-builders", "requestjson"], LambdaBuildContainer._get_entrypoint("requestjson")) + + +class TestLambdaBuildContainer_convert_to_container_dirs(TestCase): + + 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.assertEquals(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.assertEquals(result, expected) + + 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.assertEquals(result, expected)