Skip to content

Commit 4f428c3

Browse files
authored
Merge pull request #681 from Xarthisius/abspath_in_scripts
[MRG] Allow absolute paths in build_script_files. Fixes #673
2 parents 37774fc + 24234d9 commit 4f428c3

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
@@ -534,6 +536,17 @@ def render(self):
534536
"RUN {}".format(textwrap.dedent(script.strip("\n")))
535537
)
536538

539+
# Based on a physical location of a build script on the host,
540+
# create a mapping between:
541+
# 1. Location of a build script in a Docker build context
542+
# ('assemble_files/<escaped-file-path-truncated>-<6-chars-of-its-hash>')
543+
# 2. Location of the aforemention script in the Docker image
544+
# Base template basically does: COPY <1.> <2.>
545+
build_script_files = {
546+
self.generate_build_context_filename(k)[0]: v
547+
for k, v in self.get_build_script_files().items()
548+
}
549+
537550
return t.render(
538551
packages=sorted(self.get_packages()),
539552
path=self.get_path(),
@@ -544,13 +557,42 @@ def render(self):
544557
preassemble_script_files=self.get_preassemble_script_files(),
545558
preassemble_script_directives=preassemble_script_directives,
546559
assemble_script_directives=assemble_script_directives,
547-
build_script_files=self.get_build_script_files(),
560+
build_script_files=build_script_files,
548561
base_packages=sorted(self.get_base_packages()),
549562
post_build_scripts=self.get_post_build_scripts(),
550563
start_script=self.get_start_script(),
551564
appendix=self.appendix,
552565
)
553566

567+
@staticmethod
568+
def generate_build_context_filename(src_path, hash_length=6):
569+
"""
570+
Generate a filename for a file injected into the Docker build context.
571+
572+
In case the src_path is relative, it's assumed it's relative to directory of
573+
this __file__. Returns the resulting filename and an absolute path to the source
574+
file on host.
575+
"""
576+
if not os.path.isabs(src_path):
577+
src_parts = src_path.split("/")
578+
src_path = os.path.join(os.path.dirname(__file__), *src_parts)
579+
580+
src_path_hash = hashlib.sha256(src_path.encode("utf-8")).hexdigest()
581+
safe_chars = set(string.ascii_letters + string.digits)
582+
583+
def escape(s):
584+
return escapism.escape(s, safe=safe_chars, escape_char="-")
585+
586+
src_path_slug = escape(src_path)
587+
filename = "build_script_files/{name}-{hash}"
588+
return (
589+
filename.format(
590+
name=src_path_slug[: 255 - hash_length - 20],
591+
hash=src_path_hash[:hash_length],
592+
).lower(),
593+
src_path,
594+
)
595+
554596
def build(
555597
self,
556598
client,
@@ -580,9 +622,8 @@ def _filter_tar(tar):
580622
return tar
581623

582624
for src in sorted(self.get_build_script_files()):
583-
src_parts = src.split("/")
584-
src_path = os.path.join(os.path.dirname(__file__), *src_parts)
585-
tar.add(src_path, src, filter=_filter_tar)
625+
dest_path, src_path = self.generate_build_context_filename(src)
626+
tar.add(src_path, dest_path, filter=_filter_tar)
586627

587628
tar.add(ENTRYPOINT_FILE, "repo2docker-entrypoint", filter=_filter_tar)
588629

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)