diff --git a/MANIFEST.in b/MANIFEST.in index 040a4bc6..f60f3f11 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,4 +2,5 @@ include test-requirements.txt include requirements.txt include requirements3.txt include README.md -include LICENSE \ No newline at end of file +include LICENSE +recursive-include tests *.py diff --git a/README.md b/README.md index b80ddcba..385193a4 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,10 @@ Our latest stable is always available on PyPi. Documentation ------------ -Full documentation is available in the `/docs/` directory. +[![Documentation Status](https://readthedocs.org/projects/docker-py/badge/?version=latest)](https://readthedocs.org/projects/docker-py/?badge=latest) + +Full documentation is hosted on [ReadTheDocs](http://docker-py.readthedocs.org/en/latest/). +Sources are available in the `docs/` directory. License diff --git a/docker/unixconn/unixconn.py b/docker/unixconn/unixconn.py index 3d3f7bc5..295d426c 100644 --- a/docker/unixconn/unixconn.py +++ b/docker/unixconn/unixconn.py @@ -25,6 +25,11 @@ try: except ImportError: import urllib3.connectionpool as connectionpool +try: + from requests.packages.urllib3._collections import RecentlyUsedContainer +except ImportError: + from urllib3._collections import RecentlyUsedContainer + class UnixHTTPConnection(httplib.HTTPConnection, object): def __init__(self, base_url, unix_socket, timeout=60): @@ -65,7 +70,22 @@ class UnixAdapter(requests.adapters.HTTPAdapter): def __init__(self, base_url, timeout=60): self.base_url = base_url self.timeout = timeout + self.pools = RecentlyUsedContainer(10, + dispose_func=lambda p: p.close()) super(UnixAdapter, self).__init__() def get_connection(self, socket_path, proxies=None): - return UnixHTTPConnectionPool(self.base_url, socket_path, self.timeout) + with self.pools.lock: + pool = self.pools.get(socket_path) + if pool: + return pool + + pool = UnixHTTPConnectionPool(self.base_url, + socket_path, + self.timeout) + self.pools[socket_path] = pool + + return pool + + def close(self): + self.pools.clear() diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index 5d2c1b87..82b1a96f 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -1,4 +1,4 @@ from .utils import ( compare_version, convert_port_bindings, convert_volume_binds, - mkbuildcontext, ping, tar, parse_repository_tag, parse_host + mkbuildcontext, ping, tar, parse_repository_tag, parse_host, kwargs_from_env ) # flake8: noqa diff --git a/docker/utils/utils.py b/docker/utils/utils.py index 452cf718..91e5e66b 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -14,6 +14,7 @@ import io import os +import os.path import tarfile import tempfile from distutils.version import StrictVersion @@ -23,6 +24,7 @@ import requests import six from .. import errors +from .. import tls DEFAULT_HTTP_HOST = "127.0.0.1" DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock" @@ -257,3 +259,23 @@ def parse_devices(devices): "PathInContainer": path_in_container, "CgroupPermissions": permissions}) return device_list + + +def kwargs_from_env(ssl_version=None, assert_hostname=None): + host = os.environ.get('DOCKER_HOST') + cert_path = os.environ.get('DOCKER_CERT_PATH') + tls_verify = os.environ.get('DOCKER_TLS_VERIFY') + + params = {} + if host: + params['base_url'] = (host.replace('tcp://', 'https://') + if tls_verify else host) + if tls_verify and cert_path: + params['tls'] = tls.TLSConfig( + client_cert=(os.path.join(cert_path, 'cert.pem'), + os.path.join(cert_path, 'key.pem')), + ca_cert=os.path.join(cert_path, 'ca.pem'), + verify=True, + ssl_version=ssl_version, + assert_hostname=assert_hostname) + return params diff --git a/tests/integration_test.py b/tests/integration_test.py index 8a39ce3b..65d974b5 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -21,6 +21,7 @@ import shutil import signal import tempfile import unittest +import warnings import docker import six @@ -1035,6 +1036,28 @@ class TestConnectionTimeout(unittest.TestCase): self.assertTrue(end - start < 2 * self.timeout) +class UnixconnTestCase(unittest.TestCase): + """ + Test UNIX socket connection adapter. + """ + + def test_resource_warnings(self): + """ + Test no warnings are produced when using the client. + """ + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + + client = docker.Client() + client.images() + client.close() + del client + + assert len(w) == 0, \ + "No warnings produced: {0}".format(w[0].message) + + if __name__ == '__main__': c = docker.Client(base_url=DEFAULT_BASE_URL) c.pull('busybox') diff --git a/tests/test.py b/tests/test.py index 598ccac5..83ad7122 100644 --- a/tests/test.py +++ b/tests/test.py @@ -99,6 +99,9 @@ class DockerClientTest(Cleanup, unittest.TestCase): # Force-clear authconfig to avoid tampering with the tests self.client._cfg = {'Configs': {}} + def tearDown(self): + self.client.close() + ######################### # INFORMATION TESTS # ######################### diff --git a/tests/testdata/certs/ca.pem b/tests/testdata/certs/ca.pem new file mode 100644 index 00000000..e69de29b diff --git a/tests/testdata/certs/cert.pem b/tests/testdata/certs/cert.pem new file mode 100644 index 00000000..e69de29b diff --git a/tests/testdata/certs/key.pem b/tests/testdata/certs/key.pem new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils_test.py b/tests/utils_test.py index 277781b8..7532d83e 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,12 +1,22 @@ import unittest from docker.errors import DockerException -from docker.utils import parse_repository_tag, parse_host +from docker.utils import parse_repository_tag, parse_host, kwargs_from_env +from docker.client import Client + +import os +import os.path class UtilsTest(unittest.TestCase): longMessage = True + def setUp(self): + self.os_environ = os.environ.copy() + + def tearDown(self): + os.environ = self.os_environ + def test_parse_repository_tag(self): self.assertEqual(parse_repository_tag("root"), ("root", None)) @@ -53,5 +63,25 @@ class UtilsTest(unittest.TestCase): for host, expected in valid_hosts.items(): self.assertEqual(parse_host(host), expected, msg=host) + def test_kwargs_from_env(self): + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=os.path.join( + os.path.dirname(__file__), + 'testdata/certs'), + DOCKER_TLS_VERIFY='1') + kwargs = kwargs_from_env(assert_hostname=False) + self.assertEquals('https://192.168.59.103:2376', kwargs['base_url']) + self.assertIn('ca.pem', kwargs['tls'].verify) + self.assertIn('cert.pem', kwargs['tls'].cert[0]) + self.assertIn('key.pem', kwargs['tls'].cert[1]) + self.assertEquals(False, kwargs['tls'].assert_hostname) + try: + client = Client(**kwargs) + self.assertEquals(kwargs['base_url'], client.base_url) + self.assertEquals(kwargs['tls'].verify, client.verify) + self.assertEquals(kwargs['tls'].cert, client.cert) + except TypeError, e: + self.fail(e) + if __name__ == '__main__': unittest.main()