Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docker/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import json
import os
import re
import shlex
import struct
Expand Down Expand Up @@ -351,7 +352,12 @@ def build(self, path=None, tag=None, quiet=False, fileobj=None,
'git://', 'github.com/')):
remote = path
else:
context = utils.tar(path)
dockerignore = os.path.join(path, '.dockerignore')
exclude = None
if os.path.exists(dockerignore):
with open(dockerignore, 'r') as f:
exclude = list(filter(bool, f.read().split('\n')))
context = utils.tar(path, exclude=exclude)

if utils.compare_version('1.8', self._version) >= 0:
stream = True
Expand Down
25 changes: 23 additions & 2 deletions docker/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
# limitations under the License.

import io
import os
import tarfile
import tempfile
from distutils.version import StrictVersion
from fnmatch import fnmatch

import requests
import six
Expand All @@ -42,10 +44,29 @@ def mkbuildcontext(dockerfile):
return f


def tar(path):
def fnmatch_any(relpath, patterns):
return any([fnmatch(relpath, pattern) for pattern in patterns])


def tar(path, exclude=None):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
t.add(path, arcname='.')
for dirpath, dirnames, filenames in os.walk(path):
relpath = os.path.relpath(dirpath, path)
if relpath == '.':
relpath = ''
if exclude is None:
fnames = filenames
else:
dirnames[:] = [d for d in dirnames
if not fnmatch_any(os.path.join(relpath, d),
exclude)]
fnames = [name for name in filenames
if not fnmatch_any(os.path.join(relpath, name),
exclude)]
for name in fnames:
arcname = os.path.join(relpath, name)
t.add(os.path.join(path, arcname), arcname=arcname)
t.close()
f.seek(0)
return f
Expand Down
40 changes: 40 additions & 0 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import json
import io
import os
import shutil
import signal
import tempfile
import unittest

import docker
import six

from tests.test import Cleanup

# FIXME: missing tests for
# export; history; import_image; insert; port; push; tag; get; load

Expand Down Expand Up @@ -820,6 +823,43 @@ def runTest(self):
self.assertEqual(logs.find('HTTP code: 403'), -1)


class TestBuildWithDockerignore(Cleanup, BaseTestCase):
def runTest(self):
if self.client._version < 1.8:
return

base_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir)

with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("\n".join([
'FROM busybox',
'MAINTAINER docker-py',
'ADD . /test',
'RUN ls -A /test',
]))

with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
f.write("\n".join([
'node_modules',
'', # empty line
]))

with open(os.path.join(base_dir, 'not-ignored'), 'w') as f:
f.write("this file should not be ignored")

subdir = os.path.join(base_dir, 'node_modules', 'grunt-cli')
os.makedirs(subdir)
with open(os.path.join(subdir, 'grunt'), 'w') as f:
f.write("grunt")

stream = self.client.build(path=base_dir, stream=True)
logs = ''
for chunk in stream:
logs += chunk
self.assertFalse('node_modules' in logs)
self.assertTrue('not-ignored' in logs)

#######################
# PY SPECIFIC TESTS #
#######################
Expand Down
52 changes: 51 additions & 1 deletion tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
import io
import json
import os
import shutil
import signal
import sys
import tarfile
import tempfile
import unittest
import gzip
Expand Down Expand Up @@ -58,9 +61,34 @@ def fake_resp(url, data=None, **kwargs):
docker.client.DEFAULT_DOCKER_API_VERSION)


class Cleanup(object):
if sys.version_info < (2, 7):
# Provide a basic implementation of addCleanup for Python < 2.7
def __init__(self, *args, **kwargs):
super(Cleanup, self).__init__(*args, **kwargs)
self._cleanups = []

def tearDown(self):
super(Cleanup, self).tearDown()
ok = True
while self._cleanups:
fn, args, kwargs = self._cleanups.pop(-1)
try:
fn(*args, **kwargs)
except KeyboardInterrupt:
raise
except:
ok = False
if not ok:
raise

def addCleanup(self, function, *args, **kwargs):
self._cleanups.append((function, args, kwargs))


@mock.patch.multiple('docker.Client', get=fake_request, post=fake_request,
put=fake_request, delete=fake_request)
class DockerClientTest(unittest.TestCase):
class DockerClientTest(Cleanup, unittest.TestCase):
def setUp(self):
self.client = docker.Client()
# Force-clear authconfig to avoid tampering with the tests
Expand Down Expand Up @@ -1350,11 +1378,13 @@ def test_build_container_custom_context_gzip(self):

def test_load_config_no_file(self):
folder = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, folder)
cfg = docker.auth.load_config(folder)
self.assertTrue(cfg is not None)

def test_load_config(self):
folder = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, folder)
f = open(os.path.join(folder, '.dockercfg'), 'w')
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
f.write('auth = {0}\n'.format(auth_))
Expand All @@ -1369,6 +1399,26 @@ def test_load_config(self):
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
self.assertEqual(cfg.get('auth'), None)

def test_tar_with_excludes(self):
base = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, base)
for d in ['test/foo', 'bar']:
os.makedirs(os.path.join(base, d))
for f in ['a.txt', 'b.py', 'other.png']:
with open(os.path.join(base, d, f), 'w') as f:
f.write("content")

for exclude, names in (
(['*.py'], ['bar/a.txt', 'bar/other.png',
'test/foo/a.txt', 'test/foo/other.png']),
(['*.png', 'bar'], ['test/foo/a.txt', 'test/foo/b.py']),
(['test/foo', 'a.txt'], ['bar/a.txt', 'bar/b.py',
'bar/other.png']),
):
archive = docker.utils.tar(base, exclude=exclude)
tar = tarfile.open(fileobj=archive)
self.assertEqual(sorted(tar.getnames()), names)


if __name__ == '__main__':
unittest.main()