From f94e769f3d7d3e2c6bb464f674e63e0e276417ff Mon Sep 17 00:00:00 2001
From: Joffrey F <joffrey@docker.com>
Date: Thu, 12 Apr 2018 12:38:27 -0700
Subject: [PATCH] Support absolute paths for in-context Dockerfiles

Signed-off-by: Joffrey F <joffrey@docker.com>
---
 docker/api/build.py                 |  21 +++++++
 setup.py                            |  16 ++++--
 tests/integration/api_build_test.py | 109 ++++++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+), 6 deletions(-)

diff --git a/docker/api/build.py b/docker/api/build.py
index 9ff2dfb..3ee3cc5 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -318,3 +318,24 @@ class BuildApiMixin(object):
                 })
         else:
             log.debug('No auth config found')
+
+
+def process_dockerfile(dockerfile, path):
+    if not dockerfile:
+        return (None, None)
+
+    abs_dockerfile = dockerfile
+    if not os.path.isabs(dockerfile):
+        abs_dockerfile = os.path.join(path, dockerfile)
+
+    if (os.path.splitdrive(path)[0] != os.path.splitdrive(abs_dockerfile)[0] or
+            os.path.relpath(abs_dockerfile, path).startswith('..')):
+        # Dockerfile not in context - read data to insert into tar later
+        with open(abs_dockerfile, 'r') as df:
+            return (
+                '.dockerfile.{0:x}'.format(random.getrandbits(160)),
+                df.read()
+            )
+
+    # Dockerfile is inside the context - return path relative to context root
+    return (os.path.relpath(abs_dockerfile, path), None)
diff --git a/setup.py b/setup.py
index 4a33c8d..875503a 100644
--- a/setup.py
+++ b/setup.py
@@ -9,12 +9,16 @@ import pip
 
 from setuptools import setup, find_packages
 
-if 'docker-py' in [x.project_name for x in pip.get_installed_distributions()]:
-    print(
-        'ERROR: "docker-py" needs to be uninstalled before installing this'
-        ' package:\npip uninstall docker-py', file=sys.stderr
-    )
-    sys.exit(1)
+try:
+    if 'docker-py' in [
+            x.project_name for x in pip.get_installed_distributions()]:
+        print(
+            'ERROR: "docker-py" needs to be uninstalled before installing this'
+            ' package:\npip uninstall docker-py', file=sys.stderr
+        )
+        sys.exit(1)
+except AttributeError:
+    pass
 
 ROOT_DIR = os.path.dirname(__file__)
 SOURCE_DIR = os.path.join(ROOT_DIR)
diff --git a/tests/integration/api_build_test.py b/tests/integration/api_build_test.py
index 8e98cc9..73e9d8f 100644
--- a/tests/integration/api_build_test.py
+++ b/tests/integration/api_build_test.py
@@ -355,3 +355,112 @@ class BuildTest(BaseAPIIntegrationTest):
     def test_build_gzip_custom_encoding(self):
         with self.assertRaises(errors.DockerException):
             self.client.build(path='.', gzip=True, encoding='text/html')
+
+    @requires_api_version('1.32')
+    @requires_experimental(until=None)
+    def test_build_invalid_platform(self):
+        script = io.BytesIO('FROM busybox\n'.encode('ascii'))
+
+        with pytest.raises(errors.APIError) as excinfo:
+            stream = self.client.build(fileobj=script, platform='foobar')
+            for _ in stream:
+                pass
+
+        assert excinfo.value.status_code == 400
+        assert 'invalid platform' in excinfo.exconly()
+
+    def test_build_out_of_context_dockerfile(self):
+        base_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, base_dir)
+        with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
+            f.write('hello world')
+        with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
+            f.write('.dockerignore\n')
+        df = tempfile.NamedTemporaryFile()
+        self.addCleanup(df.close)
+        df.write(('\n'.join([
+            'FROM busybox',
+            'COPY . /src',
+            'WORKDIR /src',
+        ])).encode('utf-8'))
+        df.flush()
+        img_name = random_name()
+        self.tmp_imgs.append(img_name)
+        stream = self.client.build(
+            path=base_dir, dockerfile=df.name, tag=img_name,
+            decode=True
+        )
+        lines = []
+        for chunk in stream:
+            lines.append(chunk)
+        assert 'Successfully tagged' in lines[-1]['stream']
+
+        ctnr = self.client.create_container(img_name, 'ls -a')
+        self.tmp_containers.append(ctnr)
+        self.client.start(ctnr)
+        lsdata = self.client.logs(ctnr).strip().split(b'\n')
+        assert len(lsdata) == 3
+        assert sorted([b'.', b'..', b'file.txt']) == sorted(lsdata)
+
+    def test_build_in_context_dockerfile(self):
+        base_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, base_dir)
+        with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
+            f.write('hello world')
+        with open(os.path.join(base_dir, 'custom.dockerfile'), 'w') as df:
+            df.write('\n'.join([
+                'FROM busybox',
+                'COPY . /src',
+                'WORKDIR /src',
+            ]))
+        img_name = random_name()
+        self.tmp_imgs.append(img_name)
+        stream = self.client.build(
+            path=base_dir, dockerfile='custom.dockerfile', tag=img_name,
+            decode=True
+        )
+        lines = []
+        for chunk in stream:
+            lines.append(chunk)
+        assert 'Successfully tagged' in lines[-1]['stream']
+
+        ctnr = self.client.create_container(img_name, 'ls -a')
+        self.tmp_containers.append(ctnr)
+        self.client.start(ctnr)
+        lsdata = self.client.logs(ctnr).strip().split(b'\n')
+        assert len(lsdata) == 4
+        assert sorted(
+            [b'.', b'..', b'file.txt', b'custom.dockerfile']
+        ) == sorted(lsdata)
+
+    def test_build_in_context_abs_dockerfile(self):
+        base_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, base_dir)
+        abs_dockerfile_path = os.path.join(base_dir, 'custom.dockerfile')
+        with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
+            f.write('hello world')
+        with open(abs_dockerfile_path, 'w') as df:
+            df.write('\n'.join([
+                'FROM busybox',
+                'COPY . /src',
+                'WORKDIR /src',
+            ]))
+        img_name = random_name()
+        self.tmp_imgs.append(img_name)
+        stream = self.client.build(
+            path=base_dir, dockerfile=abs_dockerfile_path, tag=img_name,
+            decode=True
+        )
+        lines = []
+        for chunk in stream:
+            lines.append(chunk)
+        assert 'Successfully tagged' in lines[-1]['stream']
+
+        ctnr = self.client.create_container(img_name, 'ls -a')
+        self.tmp_containers.append(ctnr)
+        self.client.start(ctnr)
+        lsdata = self.client.logs(ctnr).strip().split(b'\n')
+        assert len(lsdata) == 4
+        assert sorted(
+            [b'.', b'..', b'file.txt', b'custom.dockerfile']
+        ) == sorted(lsdata)
-- 
2.15.1

