Skip to content

Commit

Permalink
Merge pull request #681 from Xarthisius/abspath_in_scripts
Browse files Browse the repository at this point in the history
[MRG] Allow absolute paths in build_script_files. Fixes #673
  • Loading branch information
minrk authored Sep 7, 2019
2 parents 37774fc + 24234d9 commit 4f428c3
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 5 deletions.
51 changes: 46 additions & 5 deletions repo2docker/buildpacks/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
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
Expand Down Expand Up @@ -534,6 +536,17 @@ def render(self):
"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(),
Expand All @@ -544,13 +557,42 @@ def render(self):
preassemble_script_files=self.get_preassemble_script_files(),
preassemble_script_directives=preassemble_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,
Expand Down Expand Up @@ -580,9 +622,8 @@ def _filter_tar(tar):
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)

Expand Down
42 changes: 42 additions & 0 deletions tests/unit/test_external_scripts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""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()

0 comments on commit 4f428c3

Please sign in to comment.