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
25 changes: 15 additions & 10 deletions docker/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@
from .. import auth
from ..constants import (
DEFAULT_TIMEOUT_SECONDS, DEFAULT_USER_AGENT, IS_WINDOWS_PLATFORM,
DEFAULT_DOCKER_API_VERSION, STREAM_HEADER_SIZE_BYTES, DEFAULT_NUM_POOLS,
MINIMUM_DOCKER_API_VERSION
DEFAULT_DOCKER_API_VERSION, MINIMUM_DOCKER_API_VERSION,
STREAM_HEADER_SIZE_BYTES, DEFAULT_NUM_POOLS_SSH, DEFAULT_NUM_POOLS
)
from ..errors import (
DockerException, InvalidVersion, TLSParameterError,
create_api_error_from_http_exception
)
from ..tls import TLSConfig
from ..transport import SSLAdapter, UnixAdapter
from ..transport import SSLHTTPAdapter, UnixHTTPAdapter
from ..utils import utils, check_resource, update_headers, config
from ..utils.socket import frames_iter, consume_socket_output, demux_adaptor
from ..utils.json_stream import json_stream
from ..utils.proxy import ProxyConfig
try:
from ..transport import NpipeAdapter
from ..transport import NpipeHTTPAdapter
except ImportError:
pass

try:
from ..transport import SSHAdapter
from ..transport import SSHHTTPAdapter
except ImportError:
pass

Expand Down Expand Up @@ -101,7 +101,7 @@ class APIClient(

def __init__(self, base_url=None, version=None,
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
user_agent=DEFAULT_USER_AGENT, num_pools=DEFAULT_NUM_POOLS,
user_agent=DEFAULT_USER_AGENT, num_pools=None,
credstore_env=None):
super(APIClient, self).__init__()

Expand Down Expand Up @@ -132,8 +132,12 @@ def __init__(self, base_url=None, version=None,
base_url = utils.parse_host(
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)
)
# SSH has a different default for num_pools to all other adapters
num_pools = num_pools or DEFAULT_NUM_POOLS_SSH if \
base_url.startswith('ssh://') else DEFAULT_NUM_POOLS

if base_url.startswith('http+unix://'):
self._custom_adapter = UnixAdapter(
self._custom_adapter = UnixHTTPAdapter(
base_url, timeout, pool_connections=num_pools
)
self.mount('http+docker://', self._custom_adapter)
Expand All @@ -147,7 +151,7 @@ def __init__(self, base_url=None, version=None,
'The npipe:// protocol is only supported on Windows'
)
try:
self._custom_adapter = NpipeAdapter(
self._custom_adapter = NpipeHTTPAdapter(
base_url, timeout, pool_connections=num_pools
)
except NameError:
Expand All @@ -158,7 +162,7 @@ def __init__(self, base_url=None, version=None,
self.base_url = 'http+docker://localnpipe'
elif base_url.startswith('ssh://'):
try:
self._custom_adapter = SSHAdapter(
self._custom_adapter = SSHHTTPAdapter(
base_url, timeout, pool_connections=num_pools
)
except NameError:
Expand All @@ -173,7 +177,8 @@ def __init__(self, base_url=None, version=None,
if isinstance(tls, TLSConfig):
tls.configure_client(self)
elif tls:
self._custom_adapter = SSLAdapter(pool_connections=num_pools)
self._custom_adapter = SSLHTTPAdapter(
pool_connections=num_pools)
self.mount('https://', self._custom_adapter)
self.base_url = base_url

Expand Down
6 changes: 6 additions & 0 deletions docker/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,10 @@

DEFAULT_USER_AGENT = "docker-sdk-python/{0}".format(version)
DEFAULT_NUM_POOLS = 25

# The OpenSSH server default value for MaxSessions is 10 which means we can
# use up to 9, leaving the final session for the underlying SSH connection.
# For more details see: https://github.com/docker/docker-py/issues/2246
DEFAULT_NUM_POOLS_SSH = 9

DEFAULT_DATA_CHUNK_SIZE = 1024 * 2048
4 changes: 2 additions & 2 deletions docker/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import ssl

from . import errors
from .transport import SSLAdapter
from .transport import SSLHTTPAdapter


class TLSConfig(object):
Expand Down Expand Up @@ -105,7 +105,7 @@ def configure_client(self, client):
if self.cert:
client.cert = self.cert

client.mount('https://', SSLAdapter(
client.mount('https://', SSLHTTPAdapter(
ssl_version=self.ssl_version,
assert_hostname=self.assert_hostname,
assert_fingerprint=self.assert_fingerprint,
Expand Down
8 changes: 4 additions & 4 deletions docker/transport/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# flake8: noqa
from .unixconn import UnixAdapter
from .ssladapter import SSLAdapter
from .unixconn import UnixHTTPAdapter
from .ssladapter import SSLHTTPAdapter
try:
from .npipeconn import NpipeAdapter
from .npipeconn import NpipeHTTPAdapter
from .npipesocket import NpipeSocket
except ImportError:
pass

try:
from .sshconn import SSHAdapter
from .sshconn import SSHHTTPAdapter
except ImportError:
pass
8 changes: 8 additions & 0 deletions docker/transport/basehttpadapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import requests.adapters


class BaseHTTPAdapter(requests.adapters.HTTPAdapter):
def close(self):
super(BaseHTTPAdapter, self).close()
if hasattr(self, 'pools'):
self.pools.clear()
8 changes: 3 additions & 5 deletions docker/transport/npipeconn.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import six
import requests.adapters

from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants
from .npipesocket import NpipeSocket

Expand Down Expand Up @@ -68,7 +69,7 @@ def _get_conn(self, timeout):
return conn or self._new_conn()


class NpipeAdapter(requests.adapters.HTTPAdapter):
class NpipeHTTPAdapter(BaseHTTPAdapter):

__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['npipe_path',
'pools',
Expand All @@ -81,7 +82,7 @@ def __init__(self, base_url, timeout=60,
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(NpipeAdapter, self).__init__()
super(NpipeHTTPAdapter, self).__init__()

def get_connection(self, url, proxies=None):
with self.pools.lock:
Expand All @@ -103,6 +104,3 @@ def request_url(self, request, proxies):
# anyway, we simply return the path URL directly.
# See also: https://github.com/docker/docker-sdk-python/issues/811
return request.path_url

def close(self):
self.pools.clear()
23 changes: 16 additions & 7 deletions docker/transport/sshconn.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import requests.adapters
import six

from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants

if six.PY3:
Expand Down Expand Up @@ -68,7 +69,7 @@ def _get_conn(self, timeout):
return conn or self._new_conn()


class SSHAdapter(requests.adapters.HTTPAdapter):
class SSHHTTPAdapter(BaseHTTPAdapter):

__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [
'pools', 'timeout', 'ssh_client',
Expand All @@ -79,22 +80,30 @@ def __init__(self, base_url, timeout=60,
self.ssh_client = paramiko.SSHClient()
self.ssh_client.load_system_host_keys()

parsed = six.moves.urllib_parse.urlparse(base_url)
self.ssh_client.connect(
parsed.hostname, parsed.port, parsed.username,
)
self.base_url = base_url
self._connect()
self.timeout = timeout
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(SSHAdapter, self).__init__()
super(SSHHTTPAdapter, self).__init__()

def _connect(self):
parsed = six.moves.urllib_parse.urlparse(self.base_url)
self.ssh_client.connect(
parsed.hostname, parsed.port, parsed.username,
)

def get_connection(self, url, proxies=None):
with self.pools.lock:
pool = self.pools.get(url)
if pool:
return pool

# Connection is closed try a reconnect
if not self.ssh_client.get_transport():
self._connect()

pool = SSHConnectionPool(
self.ssh_client, self.timeout
)
Expand All @@ -103,5 +112,5 @@ def get_connection(self, url, proxies=None):
return pool

def close(self):
self.pools.clear()
super(SSHHTTPAdapter, self).close()
self.ssh_client.close()
8 changes: 5 additions & 3 deletions docker/transport/ssladapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from distutils.version import StrictVersion
from requests.adapters import HTTPAdapter

from docker.transport.basehttpadapter import BaseHTTPAdapter

try:
import requests.packages.urllib3 as urllib3
except ImportError:
Expand All @@ -22,7 +24,7 @@
urllib3.connection.match_hostname = match_hostname


class SSLAdapter(HTTPAdapter):
class SSLHTTPAdapter(BaseHTTPAdapter):
'''An HTTPS Transport Adapter that uses an arbitrary SSL version.'''

__attrs__ = HTTPAdapter.__attrs__ + ['assert_fingerprint',
Expand All @@ -34,7 +36,7 @@ def __init__(self, ssl_version=None, assert_hostname=None,
self.ssl_version = ssl_version
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
super(SSLAdapter, self).__init__(**kwargs)
super(SSLHTTPAdapter, self).__init__(**kwargs)

def init_poolmanager(self, connections, maxsize, block=False):
kwargs = {
Expand All @@ -57,7 +59,7 @@ def get_connection(self, *args, **kwargs):

But we still need to take care of when there is a proxy poolmanager
"""
conn = super(SSLAdapter, self).get_connection(*args, **kwargs)
conn = super(SSLHTTPAdapter, self).get_connection(*args, **kwargs)
if conn.assert_hostname != self.assert_hostname:
conn.assert_hostname = self.assert_hostname
return conn
Expand Down
8 changes: 3 additions & 5 deletions docker/transport/unixconn.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import socket
from six.moves import http_client as httplib

from docker.transport.basehttpadapter import BaseHTTPAdapter
from .. import constants

try:
Expand Down Expand Up @@ -69,7 +70,7 @@ def _new_conn(self):
)


class UnixAdapter(requests.adapters.HTTPAdapter):
class UnixHTTPAdapter(BaseHTTPAdapter):

__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['pools',
'socket_path',
Expand All @@ -85,7 +86,7 @@ def __init__(self, socket_url, timeout=60,
self.pools = RecentlyUsedContainer(
pool_connections, dispose_func=lambda p: p.close()
)
super(UnixAdapter, self).__init__()
super(UnixHTTPAdapter, self).__init__()

def get_connection(self, url, proxies=None):
with self.pools.lock:
Expand All @@ -107,6 +108,3 @@ def request_url(self, request, proxies):
# anyway, we simply return the path URL directly.
# See also: https://github.com/docker/docker-py/issues/811
return request.path_url

def close(self):
self.pools.clear()
2 changes: 1 addition & 1 deletion docker/version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
version = "3.7.0"
version = "3.7.1"
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])
13 changes: 13 additions & 0 deletions docs/change-log.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
Change log
==========

3.7.1
-----

[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/58?closed=1)

### Bugfixes

* Set a different default number (which is now 9) for SSH pools
* Adds a BaseHTTPAdapter with a close method to ensure that the
pools is clean on close()
* Makes SSHHTTPAdapter reopen a closed connection when needed
like the others

3.7.0
-----

Expand Down