Repository: docker/docker-py

Issue:
[1.7] regression in .dockerignore handling
If the `Dockerfile` is being ignored by a path in the `.dockerignore` file, it is incorrectly being removed from the context. There is a special case handling when the file is being excluded directly, but it should also apply when there is a path which includes the `Dockerfile`.

Possibly caused by #863

```
docker-py version: 1.7.0

$ tree -a
.
├── Docker
│   ├── dc.yml
│   └── Dockerfile.debug
└── .dockerignore

1 directory, 3 files

$ cat Docker/Dockerfile.debug

FROM alpine:3.3
RUN  echo building
CMD  echo run

$ cat Docker/dc.yml

app:
  build: ..
  dockerfile: Docker/Dockerfile.debug

$ cat .dockerignore
Docker/

docker-compose -f Docker/dc.yml build
ERROR: Cannot locate specified Dockerfile: Docker/Dockerfile.debug
```



Discussion:

Resolving patch:
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -128,7 +128,13 @@ def exclude_paths(root, patterns, dockerfile=None):
     paths = get_paths(root, exclude_patterns, include_patterns,
                       has_exceptions=len(exceptions) > 0)
 
-    return set(paths)
+    return set(paths).union(
+        # If the Dockerfile is in a subdirectory that is excluded, get_paths
+        # will not descend into it and the file will be skipped. This ensures
+        # it doesn't happen.
+        set([dockerfile])
+        if os.path.exists(os.path.join(root, dockerfile)) else set()
+    )
 
 
 def should_include(path, exclude_patterns, include_patterns):


Test patch:
diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py
--- a/tests/unit/utils_test.py
+++ b/tests/unit/utils_test.py
@@ -736,6 +736,7 @@ class ExcludePathsTest(base.BaseTestCase):
         'foo/b.py',
         'foo/bar/a.py',
         'bar/a.py',
+        'foo/Dockerfile3',
     ]
 
     all_paths = set(dirs + files)
@@ -775,6 +776,14 @@ def test_exclude_custom_dockerfile(self):
         assert self.exclude(['*'], dockerfile='Dockerfile.alt') == \
             set(['Dockerfile.alt', '.dockerignore'])
 
+        assert self.exclude(['*'], dockerfile='foo/Dockerfile3') == \
+            set(['foo/Dockerfile3', '.dockerignore'])
+
+    def test_exclude_dockerfile_child(self):
+        includes = self.exclude(['foo/'], dockerfile='foo/Dockerfile3')
+        assert 'foo/Dockerfile3' in includes
+        assert 'foo/a.py' not in includes
+
     def test_single_filename(self):
         assert self.exclude(['a.py']) == self.all_paths - set(['a.py'])
 
@@ -825,28 +834,31 @@ def test_wildcard_subdir_wildcard_filename(self):
     def test_directory(self):
         assert self.exclude(['foo']) == self.all_paths - set([
             'foo', 'foo/a.py', 'foo/b.py',
-            'foo/bar', 'foo/bar/a.py',
+            'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3'
         ])
 
     def test_directory_with_trailing_slash(self):
         assert self.exclude(['foo']) == self.all_paths - set([
             'foo', 'foo/a.py', 'foo/b.py',
-            'foo/bar', 'foo/bar/a.py',
+            'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3'
         ])
 
     def test_directory_with_single_exception(self):
         assert self.exclude(['foo', '!foo/bar/a.py']) == self.all_paths - set([
-            'foo/a.py', 'foo/b.py', 'foo', 'foo/bar'
+            'foo/a.py', 'foo/b.py', 'foo', 'foo/bar',
+            'foo/Dockerfile3'
         ])
 
     def test_directory_with_subdir_exception(self):
         assert self.exclude(['foo', '!foo/bar']) == self.all_paths - set([
-            'foo/a.py', 'foo/b.py', 'foo'
+            'foo/a.py', 'foo/b.py', 'foo',
+            'foo/Dockerfile3'
         ])
 
     def test_directory_with_wildcard_exception(self):
         assert self.exclude(['foo', '!foo/*.py']) == self.all_paths - set([
-            'foo/bar', 'foo/bar/a.py', 'foo'
+            'foo/bar', 'foo/bar/a.py', 'foo',
+            'foo/Dockerfile3'
         ])
 
     def test_subdirectory(self):