Skip to content

Commit 24234d9

Browse files
committed
Allow absolute paths in build_script_files. Fixes #673
1 parent 2cded9b commit 24234d9

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

repo2docker/buildpacks/base.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import os
66
import re
77
import logging
8-
import docker
8+
import string
99
import sys
10+
import hashlib
11+
import escapism
1012
import xml.etree.ElementTree as ET
1113

1214
from traitlets import Dict
@@ -509,6 +511,17 @@ def render(self):
509511
"RUN {}".format(textwrap.dedent(script.strip("\n")))
510512
)
511513

514+
# Based on a physical location of a build script on the host,
515+
# create a mapping between:
516+
# 1. Location of a build script in a Docker build context
517+
# ('assemble_files/<escaped-file-path-truncated>-<6-chars-of-its-hash>')
518+
# 2. Location of the aforemention script in the Docker image
519+
# Base template basically does: COPY <1.> <2.>
520+
build_script_files = {
521+
self.generate_build_context_filename(k)[0]: v
522+
for k, v in self.get_build_script_files().items()
523+
}
524+
512525
return t.render(
513526
packages=sorted(self.get_packages()),
514527
path=self.get_path(),
@@ -518,13 +531,42 @@ def render(self):
518531
build_script_directives=build_script_directives,
519532
pre_assemble_script_directives=pre_assemble_script_directives,
520533
assemble_script_directives=assemble_script_directives,
521-
build_script_files=self.get_build_script_files(),
534+
build_script_files=build_script_files,
522535
base_packages=sorted(self.get_base_packages()),
523536
post_build_scripts=self.get_post_build_scripts(),
524537
start_script=self.get_start_script(),
525538
appendix=self.appendix,
526539
)
527540

541+
@staticmethod
542+
def generate_build_context_filename(src_path, hash_length=6):
543+
"""
544+
Generate a filename for a file injected into the Docker build context.
545+
546+
In case the src_path is relative, it's assumed it's relative to directory of
547+
this __file__. Returns the resulting filename and an absolute path to the source
548+
file on host.
549+
"""
550+
if not os.path.isabs(src_path):
551+
src_parts = src_path.split("/")
552+
src_path = os.path.join(os.path.dirname(__file__), *src_parts)
553+
554+
src_path_hash = hashlib.sha256(src_path.encode("utf-8")).hexdigest()
555+
safe_chars = set(string.ascii_letters + string.digits)
556+
557+
def escape(s):
558+
return escapism.escape(s, safe=safe_chars, escape_char="-")
559+
560+
src_path_slug = escape(src_path)
561+
filename = "build_script_files/{name}-{hash}"
562+
return (
563+
filename.format(
564+
name=src_path_slug[: 255 - hash_length - 20],
565+
hash=src_path_hash[:hash_length],
566+
).lower(),
567+
src_path,
568+
)
569+
528570
def build(
529571
self,
530572
client,
@@ -554,9 +596,8 @@ def _filter_tar(tar):
554596
return tar
555597

556598
for src in sorted(self.get_build_script_files()):
557-
src_parts = src.split("/")
558-
src_path = os.path.join(os.path.dirname(__file__), *src_parts)
559-
tar.add(src_path, src, filter=_filter_tar)
599+
dest_path, src_path = self.generate_build_context_filename(src)
600+
tar.add(src_path, dest_path, filter=_filter_tar)
560601

561602
tar.add(ENTRYPOINT_FILE, "repo2docker-entrypoint", filter=_filter_tar)
562603

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Test if assemble scripts from outside of r2d repo are accepted."""
2+
import time
3+
from repo2docker.app import Repo2Docker
4+
from repo2docker.buildpacks import PythonBuildPack
5+
6+
7+
def test_Repo2Docker_external_build_scripts(tmpdir):
8+
tempfile = tmpdir.join("absolute-script")
9+
tempfile.write("Hello World of Absolute Paths!")
10+
11+
class MockBuildPack(PythonBuildPack):
12+
def detect(self):
13+
return True
14+
15+
def get_build_script_files(self):
16+
files = {str(tempfile): "/tmp/my_extra_script"}
17+
files.update(super().get_build_script_files())
18+
return files
19+
20+
app = Repo2Docker(repo=str(tmpdir))
21+
app.buildpacks = [MockBuildPack]
22+
app.initialize()
23+
app.build()
24+
container = app.start_container()
25+
26+
# give the container a chance to start
27+
tic = 180
28+
while container.status != "running" or tic < 0:
29+
time.sleep(1)
30+
tic -= 1
31+
32+
assert container.status == "running"
33+
34+
try:
35+
status, output = container.exec_run(["sh", "-c", "cat /tmp/my_extra_script"])
36+
assert status == 0
37+
assert output.decode("utf-8") == "Hello World of Absolute Paths!"
38+
finally:
39+
container.stop(timeout=1)
40+
container.reload()
41+
assert container.status == "exited", container.status
42+
container.remove()

0 commit comments

Comments
 (0)