Skip to content

Commit 77c3e57

Browse files
committed
Support building with Dockerfile outside of context
Signed-off-by: Joffrey F <[email protected]>
1 parent 20939d0 commit 77c3e57

File tree

4 files changed

+70
-7
lines changed

4 files changed

+70
-7
lines changed

docker/api/build.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import logging
33
import os
4+
import random
45

56
from .. import auth
67
from .. import constants
@@ -148,6 +149,15 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None,
148149
lambda x: x != '' and x[0] != '#',
149150
[l.strip() for l in f.read().splitlines()]
150151
))
152+
if dockerfile and os.path.relpath(dockerfile, path).startswith(
153+
'..'):
154+
with open(dockerfile, 'r') as df:
155+
dockerfile = (
156+
'.dockerfile.{0:x}'.format(random.getrandbits(160)),
157+
df.read()
158+
)
159+
else:
160+
dockerfile = (dockerfile, None)
151161
context = utils.tar(
152162
path, exclude=exclude, dockerfile=dockerfile, gzip=gzip
153163
)

docker/utils/build.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,33 @@
22
import re
33

44
from ..constants import IS_WINDOWS_PLATFORM
5+
from .utils import create_archive
56
from fnmatch import fnmatch
67
from itertools import chain
7-
from .utils import create_archive
8+
9+
10+
_SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')
811

912

1013
def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
1114
root = os.path.abspath(path)
1215
exclude = exclude or []
16+
dockerfile = dockerfile or (None, None)
17+
extra_files = []
18+
if dockerfile[1] is not None:
19+
dockerignore_contents = '\n'.join(
20+
(exclude or ['.dockerignore']) + [dockerfile[0]]
21+
)
22+
extra_files = [
23+
('.dockerignore', dockerignore_contents),
24+
dockerfile,
25+
]
1326
return create_archive(
14-
files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile)),
15-
root=root, fileobj=fileobj, gzip=gzip
27+
files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile[0])),
28+
root=root, fileobj=fileobj, gzip=gzip, extra_files=extra_files
1629
)
1730

1831

19-
_SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/')
20-
21-
2232
def exclude_paths(root, patterns, dockerfile=None):
2333
"""
2434
Given a root directory path and a list of .dockerignore patterns, return

docker/utils/utils.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,17 @@ def build_file_list(root):
8888
return files
8989

9090

91-
def create_archive(root, files=None, fileobj=None, gzip=False):
91+
def create_archive(root, files=None, fileobj=None, gzip=False,
92+
extra_files=None):
9293
if not fileobj:
9394
fileobj = tempfile.NamedTemporaryFile()
9495
t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
9596
if files is None:
9697
files = build_file_list(root)
9798
for path in files:
99+
if path in [e[0] for e in extra_files]:
100+
# Extra files override context files with the same name
101+
continue
98102
full_path = os.path.join(root, path)
99103

100104
i = t.gettarinfo(full_path, arcname=path)
@@ -123,6 +127,12 @@ def create_archive(root, files=None, fileobj=None, gzip=False):
123127
else:
124128
# Directories, FIFOs, symlinks... don't need to be read.
125129
t.addfile(i, None)
130+
131+
for name, contents in extra_files:
132+
info = tarfile.TarInfo(name)
133+
info.size = len(contents)
134+
t.addfile(info, io.BytesIO(contents.encode('utf-8')))
135+
126136
t.close()
127137
fileobj.seek(0)
128138
return fileobj

tests/integration/api_build_test.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,36 @@ def test_build_invalid_platform(self):
407407

408408
assert excinfo.value.status_code == 400
409409
assert 'invalid platform' in excinfo.exconly()
410+
411+
def test_build_out_of_context_dockerfile(self):
412+
base_dir = tempfile.mkdtemp()
413+
self.addCleanup(shutil.rmtree, base_dir)
414+
with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
415+
f.write('hello world')
416+
with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
417+
f.write('.dockerignore\n')
418+
df = tempfile.NamedTemporaryFile()
419+
self.addCleanup(df.close)
420+
df.write(('\n'.join([
421+
'FROM busybox',
422+
'COPY . /src',
423+
'WORKDIR /src',
424+
])).encode('utf-8'))
425+
df.flush()
426+
img_name = random_name()
427+
self.tmp_imgs.append(img_name)
428+
stream = self.client.build(
429+
path=base_dir, dockerfile=df.name, tag=img_name,
430+
decode=True
431+
)
432+
lines = []
433+
for chunk in stream:
434+
lines.append(chunk)
435+
assert 'Successfully tagged' in lines[-1]['stream']
436+
437+
ctnr = self.client.create_container(img_name, 'ls -a')
438+
self.tmp_containers.append(ctnr)
439+
self.client.start(ctnr)
440+
lsdata = self.client.logs(ctnr).strip().split(b'\n')
441+
assert len(lsdata) == 3
442+
assert sorted([b'.', b'..', b'file.txt']) == sorted(lsdata)

0 commit comments

Comments
 (0)