Kaydet (Commit) 87b4d327 authored tarafından Bruno Renié's avatar Bruno Renié

Add support for .dockerignore

Fixes #265.

Implementation is a bit more elaborate than docker's implementation and
matches with the one proposed in dotcloud/docker#6869 to handle permission
issues more nicely.
üst 91702191
......@@ -13,6 +13,7 @@
# limitations under the License.
import json
import os
import re
import shlex
import struct
......@@ -351,7 +352,12 @@ class Client(requests.Session):
'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
......
......@@ -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
......@@ -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
......
......@@ -17,6 +17,7 @@ import base64
import json
import io
import os
import shutil
import signal
import tempfile
import unittest
......@@ -24,6 +25,8 @@ 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
......@@ -820,6 +823,43 @@ class TestBuildWithAuth(BaseTestCase):
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 #
#######################
......
......@@ -17,7 +17,10 @@ import datetime
import io
import json
import os
import shutil
import signal
import sys
import tarfile
import tempfile
import unittest
import gzip
......@@ -58,9 +61,34 @@ url_prefix = 'http+unix://var/run/docker.sock/v{0}/'.format(
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
......@@ -1350,11 +1378,13 @@ class DockerClientTest(unittest.TestCase):
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_))
......@@ -1369,6 +1399,26 @@ class DockerClientTest(unittest.TestCase):
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()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment