Allow absolute paths in build_script_files. Fixes
MODIFY
repo2docker/buildpacks/base.py
repo2docker/buildpacks/base.py
@@ -5,8 +5,10 @@ import io
import os
import re
import logging
-import docker
+import string
import sys
+import hashlib
+import escapism
import xml.etree.ElementTree as ET
from traitlets import Dict
@@ -509,6 +511,17 @@ class BuildPack:
"RUN {}".format(textwrap.dedent(script.strip("\n")))
)
+ # Based on a physical location of a build script on the host,
+ # create a mapping between:
+ # 1. Location of a build script in a Docker build context
+ # ('assemble_files/<escaped-file-path-truncated>-<6-chars-of-its-hash>')
+ # 2. Location of the aforemention script in the Docker image
+ # Base template basically does: COPY <1.> <2.>
+ build_script_files = {
+ self.generate_build_context_filename(k)[0]: v
+ for k, v in self.get_build_script_files().items()
+ }
+
return t.render(
packages=sorted(self.get_packages()),
path=self.get_path(),
@@ -518,13 +531,42 @@ class BuildPack:
build_script_directives=build_script_directives,
pre_assemble_script_directives=pre_assemble_script_directives,
assemble_script_directives=assemble_script_directives,
- build_script_files=self.get_build_script_files(),
+ build_script_files=build_script_files,
base_packages=sorted(self.get_base_packages()),
post_build_scripts=self.get_post_build_scripts(),
start_script=self.get_start_script(),
appendix=self.appendix,
)
+ @staticmethod
+ def generate_build_context_filename(src_path, hash_length=6):
+ """
+ Generate a filename for a file injected into the Docker build context.
+
+ In case the src_path is relative, it's assumed it's relative to directory of
+ this __file__. Returns the resulting filename and an absolute path to the source
+ file on host.
+ """
+ if not os.path.isabs(src_path):
+ src_parts = src_path.split("/")
+ src_path = os.path.join(os.path.dirname(__file__), *src_parts)
+
+ src_path_hash = hashlib.sha256(src_path.encode("utf-8")).hexdigest()
+ safe_chars = set(string.ascii_letters + string.digits)
+
+ def escape(s):
+ return escapism.escape(s, safe=safe_chars, escape_char="-")
+
+ src_path_slug = escape(src_path)
+ filename = "build_script_files/{name}-{hash}"
+ return (
+ filename.format(
+ name=src_path_slug[: 255 - hash_length - 20],
+ hash=src_path_hash[:hash_length],
+ ).lower(),
+ src_path,
+ )
+
def build(
self,
client,
@@ -554,9 +596,8 @@ class BuildPack:
return tar
for src in sorted(self.get_build_script_files()):
- src_parts = src.split("/")
- src_path = os.path.join(os.path.dirname(__file__), *src_parts)
- tar.add(src_path, src, filter=_filter_tar)
+ dest_path, src_path = self.generate_build_context_filename(src)
+ tar.add(src_path, dest_path, filter=_filter_tar)
tar.add(ENTRYPOINT_FILE, "repo2docker-entrypoint", filter=_filter_tar)

ADD
tests/unit/test_external_scripts.py
+"""Test if assemble scripts from outside of r2d repo are accepted."""
+import time
+from repo2docker.app import Repo2Docker
+from repo2docker.buildpacks import PythonBuildPack
+
+
+def test_Repo2Docker_external_build_scripts(tmpdir):
+ tempfile = tmpdir.join("absolute-script")
+ tempfile.write("Hello World of Absolute Paths!")
+
+ class MockBuildPack(PythonBuildPack):
+ def detect(self):
+ return True
+
+ def get_build_script_files(self):
+ files = {str(tempfile): "/tmp/my_extra_script"}
+ files.update(super().get_build_script_files())
+ return files
+
+ app = Repo2Docker(repo=str(tmpdir))
+ app.buildpacks = [MockBuildPack]
+ app.initialize()
+ app.build()
+ container = app.start_container()
+
+ # give the container a chance to start
+ tic = 180
+ while container.status != "running" or tic < 0:
+ time.sleep(1)
+ tic -= 1
+
+ assert container.status == "running"
+
+ try:
+ status, output = container.exec_run(["sh", "-c", "cat /tmp/my_extra_script"])
+ assert status == 0
+ assert output.decode("utf-8") == "Hello World of Absolute Paths!"
+ finally:
+ container.stop(timeout=1)
+ container.reload()
+ assert container.status == "exited", container.status
+ container.remove()

Allow absolute paths in build_script_files. Fixes #673