mirror of https://github.com/docker/docker-py.git
Compare commits
39 Commits
Author | SHA1 | Date |
---|---|---|
|
98eadb9f98 | |
|
93e02ab207 | |
|
aa2ea7f7d5 | |
|
0e82a7723f | |
|
672db57151 | |
|
50af8c8b01 | |
|
e7bdfe64b7 | |
|
94f9627eb7 | |
|
d7b6861e84 | |
|
ade4a52073 | |
|
92e0e9c30d | |
|
b701d5c999 | |
|
3679ffcca8 | |
|
360be5987d | |
|
26a8b5fc21 | |
|
285d1a3de4 | |
|
8def46c01e | |
|
3e62517c61 | |
|
dd1d572b4f | |
|
c4775504a6 | |
|
654e2d665c | |
|
4ebeb36b46 | |
|
7f717753e9 | |
|
a13b72ae01 | |
|
ca3d5feb67 | |
|
e61b2aabf0 | |
|
fb507738f3 | |
|
f915eef46f | |
|
4f299822fd | |
|
da26073c75 | |
|
c5022e1491 | |
|
6e47f7ccf7 | |
|
597f1a27b4 | |
|
f18c038b9f | |
|
f464d9a430 | |
|
3a8565029b | |
|
817023f9ed | |
|
9f5e35f5fe | |
|
d0467badfb |
|
@ -0,0 +1,27 @@
|
|||
name: Python package
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r test-requirements.txt -r requirements.txt
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
docker logout
|
||||
rm -rf ~/.docker
|
||||
py.test -v --cov=docker tests/unit
|
20
.travis.yml
20
.travis.yml
|
@ -1,20 +0,0 @@
|
|||
sudo: false
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
- python: 3.5
|
||||
env: TOXENV=py35
|
||||
- python: 3.6
|
||||
env: TOXENV=py36
|
||||
- python: 3.7
|
||||
env: TOXENV=py37
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- env: TOXENV=flake8
|
||||
|
||||
install:
|
||||
- pip install tox==2.9.1
|
||||
script:
|
||||
- tox
|
|
@ -1,6 +1,6 @@
|
|||
#!groovy
|
||||
|
||||
def imageNameBase = "dockerbuildbot/docker-py"
|
||||
def imageNameBase = "dockerpinata/docker-py"
|
||||
def imageNamePy2
|
||||
def imageNamePy3
|
||||
def imageDindSSH
|
||||
|
@ -18,24 +18,25 @@ def buildImage = { name, buildargs, pyTag ->
|
|||
}
|
||||
|
||||
def buildImages = { ->
|
||||
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2", cleanWorkspace: true) {
|
||||
wrappedNode(label: "amd64 && ubuntu-2004 && overlay2", cleanWorkspace: true) {
|
||||
stage("build image") {
|
||||
checkout(scm)
|
||||
|
||||
imageNamePy2 = "${imageNameBase}:py2-${gitCommit()}"
|
||||
imageNamePy3 = "${imageNameBase}:py3-${gitCommit()}"
|
||||
imageDindSSH = "${imageNameBase}:sshdind-${gitCommit()}"
|
||||
|
||||
buildImage(imageDindSSH, "-f tests/Dockerfile-ssh-dind .", "")
|
||||
buildImage(imageNamePy2, "-f tests/Dockerfile --build-arg PYTHON_VERSION=2.7 .", "py2.7")
|
||||
buildImage(imageNamePy3, "-f tests/Dockerfile --build-arg PYTHON_VERSION=3.7 .", "py3.7")
|
||||
withDockerRegistry(credentialsId:'dockerbuildbot-index.docker.io') {
|
||||
buildImage(imageDindSSH, "-f tests/Dockerfile-ssh-dind .", "")
|
||||
buildImage(imageNamePy2, "-f tests/Dockerfile --build-arg PYTHON_VERSION=2.7 .", "py2.7")
|
||||
buildImage(imageNamePy3, "-f tests/Dockerfile --build-arg PYTHON_VERSION=3.7 .", "py3.7")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getDockerVersions = { ->
|
||||
def dockerVersions = ["19.03.12"]
|
||||
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2") {
|
||||
wrappedNode(label: "amd64 && ubuntu-2004 && overlay2") {
|
||||
def result = sh(script: """docker run --rm \\
|
||||
--entrypoint=python \\
|
||||
${imageNamePy3} \\
|
||||
|
@ -76,47 +77,57 @@ def runTests = { Map settings ->
|
|||
}
|
||||
|
||||
{ ->
|
||||
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2", cleanWorkspace: true) {
|
||||
wrappedNode(label: "amd64 && ubuntu-2004 && overlay2", cleanWorkspace: true) {
|
||||
stage("test python=${pythonVersion} / docker=${dockerVersion}") {
|
||||
checkout(scm)
|
||||
def dindContainerName = "dpy-dind-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
|
||||
def testContainerName = "dpy-tests-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
|
||||
def testNetwork = "dpy-testnet-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"
|
||||
try {
|
||||
sh """docker network create ${testNetwork}"""
|
||||
sh """docker run --rm -d --name ${dindContainerName} -v /tmp --privileged --network ${testNetwork} \\
|
||||
${imageDindSSH} dockerd -H tcp://0.0.0.0:2375
|
||||
"""
|
||||
sh """docker run --rm \\
|
||||
--name ${testContainerName} \\
|
||||
-e "DOCKER_HOST=tcp://${dindContainerName}:2375" \\
|
||||
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
|
||||
--network ${testNetwork} \\
|
||||
--volumes-from ${dindContainerName} \\
|
||||
${testImage} \\
|
||||
py.test -v -rxs --cov=docker --ignore=tests/ssh tests/
|
||||
"""
|
||||
sh """docker stop ${dindContainerName}"""
|
||||
|
||||
// start DIND container with SSH
|
||||
sh """docker run --rm -d --name ${dindContainerName} -v /tmp --privileged --network ${testNetwork} \\
|
||||
${imageDindSSH} dockerd --experimental"""
|
||||
sh """docker exec ${dindContainerName} sh -c /usr/sbin/sshd """
|
||||
// run SSH tests only
|
||||
sh """docker run --rm \\
|
||||
--name ${testContainerName} \\
|
||||
-e "DOCKER_HOST=ssh://${dindContainerName}:22" \\
|
||||
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
|
||||
--network ${testNetwork} \\
|
||||
--volumes-from ${dindContainerName} \\
|
||||
${testImage} \\
|
||||
py.test -v -rxs --cov=docker tests/ssh
|
||||
"""
|
||||
} finally {
|
||||
sh """
|
||||
docker stop ${dindContainerName}
|
||||
docker network rm ${testNetwork}
|
||||
"""
|
||||
withDockerRegistry(credentialsId:'dockerbuildbot-index.docker.io') {
|
||||
try {
|
||||
// unit tests
|
||||
sh """docker run --rm \\
|
||||
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
|
||||
${testImage} \\
|
||||
py.test -v -rxs --cov=docker tests/unit
|
||||
"""
|
||||
// integration tests
|
||||
sh """docker network create ${testNetwork}"""
|
||||
sh """docker run --rm -d --name ${dindContainerName} -v /tmp --privileged --network ${testNetwork} \\
|
||||
${imageDindSSH} dockerd -H tcp://0.0.0.0:2375
|
||||
"""
|
||||
sh """docker run --rm \\
|
||||
--name ${testContainerName} \\
|
||||
-e "DOCKER_HOST=tcp://${dindContainerName}:2375" \\
|
||||
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
|
||||
--network ${testNetwork} \\
|
||||
--volumes-from ${dindContainerName} \\
|
||||
-v $DOCKER_CONFIG/config.json:/root/.docker/config.json \\
|
||||
${testImage} \\
|
||||
py.test -v -rxs --cov=docker tests/integration
|
||||
"""
|
||||
sh """docker stop ${dindContainerName}"""
|
||||
// start DIND container with SSH
|
||||
sh """docker run --rm -d --name ${dindContainerName} -v /tmp --privileged --network ${testNetwork} \\
|
||||
${imageDindSSH} dockerd --experimental"""
|
||||
sh """docker exec ${dindContainerName} sh -c /usr/sbin/sshd """
|
||||
// run SSH tests only
|
||||
sh """docker run --rm \\
|
||||
--name ${testContainerName} \\
|
||||
-e "DOCKER_HOST=ssh://${dindContainerName}:22" \\
|
||||
-e 'DOCKER_TEST_API_VERSION=${apiVersion}' \\
|
||||
--network ${testNetwork} \\
|
||||
--volumes-from ${dindContainerName} \\
|
||||
-v $DOCKER_CONFIG/config.json:/root/.docker/config.json \\
|
||||
${testImage} \\
|
||||
py.test -v -rxs --cov=docker tests/ssh
|
||||
"""
|
||||
} finally {
|
||||
sh """
|
||||
docker stop ${dindContainerName}
|
||||
docker network rm ${testNetwork}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ You can stream logs:
|
|||
|
||||
```python
|
||||
>>> for line in container.logs(stream=True):
|
||||
... print line.strip()
|
||||
... print(line.strip())
|
||||
Reticulating spline 2...
|
||||
Reticulating spline 3...
|
||||
...
|
||||
|
|
|
@ -9,9 +9,9 @@ import websocket
|
|||
|
||||
from .. import auth
|
||||
from ..constants import (DEFAULT_NUM_POOLS, DEFAULT_NUM_POOLS_SSH,
|
||||
DEFAULT_TIMEOUT_SECONDS, DEFAULT_USER_AGENT,
|
||||
IS_WINDOWS_PLATFORM, MINIMUM_DOCKER_API_VERSION,
|
||||
STREAM_HEADER_SIZE_BYTES)
|
||||
DEFAULT_MAX_POOL_SIZE, DEFAULT_TIMEOUT_SECONDS,
|
||||
DEFAULT_USER_AGENT, IS_WINDOWS_PLATFORM,
|
||||
MINIMUM_DOCKER_API_VERSION, STREAM_HEADER_SIZE_BYTES)
|
||||
from ..errors import (DockerException, InvalidVersion, TLSParameterError,
|
||||
create_api_error_from_http_exception)
|
||||
from ..tls import TLSConfig
|
||||
|
@ -92,6 +92,8 @@ class APIClient(
|
|||
use_ssh_client (bool): If set to `True`, an ssh connection is made
|
||||
via shelling out to the ssh client. Ensure the ssh client is
|
||||
installed and configured on the host.
|
||||
max_pool_size (int): The maximum number of connections
|
||||
to save in the pool.
|
||||
"""
|
||||
|
||||
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
|
||||
|
@ -103,7 +105,8 @@ class APIClient(
|
|||
def __init__(self, base_url=None, version=None,
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
|
||||
user_agent=DEFAULT_USER_AGENT, num_pools=None,
|
||||
credstore_env=None, use_ssh_client=False):
|
||||
credstore_env=None, use_ssh_client=False,
|
||||
max_pool_size=DEFAULT_MAX_POOL_SIZE):
|
||||
super(APIClient, self).__init__()
|
||||
|
||||
if tls and not base_url:
|
||||
|
@ -139,7 +142,8 @@ class APIClient(
|
|||
|
||||
if base_url.startswith('http+unix://'):
|
||||
self._custom_adapter = UnixHTTPAdapter(
|
||||
base_url, timeout, pool_connections=num_pools
|
||||
base_url, timeout, pool_connections=num_pools,
|
||||
max_pool_size=max_pool_size
|
||||
)
|
||||
self.mount('http+docker://', self._custom_adapter)
|
||||
self._unmount('http://', 'https://')
|
||||
|
@ -153,7 +157,8 @@ class APIClient(
|
|||
)
|
||||
try:
|
||||
self._custom_adapter = NpipeHTTPAdapter(
|
||||
base_url, timeout, pool_connections=num_pools
|
||||
base_url, timeout, pool_connections=num_pools,
|
||||
max_pool_size=max_pool_size
|
||||
)
|
||||
except NameError:
|
||||
raise DockerException(
|
||||
|
@ -165,7 +170,7 @@ class APIClient(
|
|||
try:
|
||||
self._custom_adapter = SSHHTTPAdapter(
|
||||
base_url, timeout, pool_connections=num_pools,
|
||||
shell_out=use_ssh_client
|
||||
max_pool_size=max_pool_size, shell_out=use_ssh_client
|
||||
)
|
||||
except NameError:
|
||||
raise DockerException(
|
||||
|
@ -492,7 +497,7 @@ class APIClient(
|
|||
Args:
|
||||
dockercfg_path (str): Use a custom path for the Docker config file
|
||||
(default ``$HOME/.docker/config.json`` if present,
|
||||
otherwise``$HOME/.dockercfg``)
|
||||
otherwise ``$HOME/.dockercfg``)
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
|
|
@ -523,6 +523,8 @@ class ContainerApiMixin(object):
|
|||
- ``container:<name|id>`` Reuse another container's network
|
||||
stack.
|
||||
- ``host`` Use the host network stack.
|
||||
This mode is incompatible with ``port_bindings``.
|
||||
|
||||
oom_kill_disable (bool): Whether to disable OOM killer.
|
||||
oom_score_adj (int): An integer value containing the score given
|
||||
to the container in order to tune OOM killer preferences.
|
||||
|
@ -531,7 +533,8 @@ class ContainerApiMixin(object):
|
|||
pids_limit (int): Tune a container's pids limit. Set ``-1`` for
|
||||
unlimited.
|
||||
port_bindings (dict): See :py:meth:`create_container`
|
||||
for more information.
|
||||
for more information.
|
||||
Imcompatible with ``host`` in ``network_mode``.
|
||||
privileged (bool): Give extended privileges to this container.
|
||||
publish_all_ports (bool): Publish all ports to the host.
|
||||
read_only (bool): Mount the container's root filesystem as read
|
||||
|
|
|
@ -109,7 +109,7 @@ class DaemonApiMixin(object):
|
|||
the Docker server.
|
||||
dockercfg_path (str): Use a custom path for the Docker config file
|
||||
(default ``$HOME/.docker/config.json`` if present,
|
||||
otherwise``$HOME/.dockercfg``)
|
||||
otherwise ``$HOME/.dockercfg``)
|
||||
|
||||
Returns:
|
||||
(dict): The response from the login request
|
||||
|
|
|
@ -81,10 +81,18 @@ class ImageApiMixin(object):
|
|||
If the server returns an error.
|
||||
"""
|
||||
params = {
|
||||
'filter': name,
|
||||
'only_ids': 1 if quiet else 0,
|
||||
'all': 1 if all else 0,
|
||||
}
|
||||
if name:
|
||||
if utils.version_lt(self._version, '1.25'):
|
||||
# only use "filter" on API 1.24 and under, as it is deprecated
|
||||
params['filter'] = name
|
||||
else:
|
||||
if filters:
|
||||
filters['reference'] = name
|
||||
else:
|
||||
filters = {'reference': name}
|
||||
if filters:
|
||||
params['filters'] = utils.convert_filters(filters)
|
||||
res = self._result(self._get(self._url("/images/json"), params=params),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from .api.client import APIClient
|
||||
from .constants import DEFAULT_TIMEOUT_SECONDS
|
||||
from .constants import (DEFAULT_TIMEOUT_SECONDS, DEFAULT_MAX_POOL_SIZE)
|
||||
from .models.configs import ConfigCollection
|
||||
from .models.containers import ContainerCollection
|
||||
from .models.images import ImageCollection
|
||||
|
@ -38,6 +38,8 @@ class DockerClient(object):
|
|||
use_ssh_client (bool): If set to `True`, an ssh connection is made
|
||||
via shelling out to the ssh client. Ensure the ssh client is
|
||||
installed and configured on the host.
|
||||
max_pool_size (int): The maximum number of connections
|
||||
to save in the pool.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.api = APIClient(*args, **kwargs)
|
||||
|
@ -67,6 +69,8 @@ class DockerClient(object):
|
|||
version (str): The version of the API to use. Set to ``auto`` to
|
||||
automatically detect the server's version. Default: ``auto``
|
||||
timeout (int): Default timeout for API calls, in seconds.
|
||||
max_pool_size (int): The maximum number of connections
|
||||
to save in the pool.
|
||||
ssl_version (int): A valid `SSL version`_.
|
||||
assert_hostname (bool): Verify the hostname of the server.
|
||||
environment (dict): The environment to read environment variables
|
||||
|
@ -86,10 +90,12 @@ class DockerClient(object):
|
|||
https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_TLSv1
|
||||
"""
|
||||
timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
|
||||
max_pool_size = kwargs.pop('max_pool_size', DEFAULT_MAX_POOL_SIZE)
|
||||
version = kwargs.pop('version', None)
|
||||
use_ssh_client = kwargs.pop('use_ssh_client', False)
|
||||
return cls(
|
||||
timeout=timeout,
|
||||
max_pool_size=max_pool_size,
|
||||
version=version,
|
||||
use_ssh_client=use_ssh_client,
|
||||
**kwargs_from_env(**kwargs)
|
||||
|
|
|
@ -36,6 +36,8 @@ DEFAULT_NUM_POOLS = 25
|
|||
# For more details see: https://github.com/docker/docker-py/issues/2246
|
||||
DEFAULT_NUM_POOLS_SSH = 9
|
||||
|
||||
DEFAULT_MAX_POOL_SIZE = 10
|
||||
|
||||
DEFAULT_DATA_CHUNK_SIZE = 1024 * 2048
|
||||
|
||||
DEFAULT_SWARM_ADDR_POOL = ['10.0.0.0/8']
|
||||
|
|
|
@ -649,6 +649,7 @@ class ContainerCollection(Collection):
|
|||
- ``container:<name|id>`` Reuse another container's network
|
||||
stack.
|
||||
- ``host`` Use the host network stack.
|
||||
This mode is incompatible with ``ports``.
|
||||
|
||||
Incompatible with ``network``.
|
||||
oom_kill_disable (bool): Whether to disable OOM killer.
|
||||
|
@ -682,6 +683,7 @@ class ContainerCollection(Collection):
|
|||
to a single container port. For example,
|
||||
``{'1111/tcp': [1234, 4567]}``.
|
||||
|
||||
Incompatible with ``host`` network mode.
|
||||
privileged (bool): Give extended privileges to this container.
|
||||
publish_all_ports (bool): Publish all ports to the host.
|
||||
read_only (bool): Mount the container's root filesystem as read
|
||||
|
|
|
@ -73,12 +73,15 @@ class NpipeHTTPAdapter(BaseHTTPAdapter):
|
|||
|
||||
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['npipe_path',
|
||||
'pools',
|
||||
'timeout']
|
||||
'timeout',
|
||||
'max_pool_size']
|
||||
|
||||
def __init__(self, base_url, timeout=60,
|
||||
pool_connections=constants.DEFAULT_NUM_POOLS):
|
||||
pool_connections=constants.DEFAULT_NUM_POOLS,
|
||||
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE):
|
||||
self.npipe_path = base_url.replace('npipe://', '')
|
||||
self.timeout = timeout
|
||||
self.max_pool_size = max_pool_size
|
||||
self.pools = RecentlyUsedContainer(
|
||||
pool_connections, dispose_func=lambda p: p.close()
|
||||
)
|
||||
|
@ -91,7 +94,8 @@ class NpipeHTTPAdapter(BaseHTTPAdapter):
|
|||
return pool
|
||||
|
||||
pool = NpipeHTTPConnectionPool(
|
||||
self.npipe_path, self.timeout
|
||||
self.npipe_path, self.timeout,
|
||||
maxsize=self.max_pool_size
|
||||
)
|
||||
self.pools[url] = pool
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import io
|
||||
import paramiko
|
||||
import requests.adapters
|
||||
import six
|
||||
import logging
|
||||
import os
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
|
@ -23,64 +23,43 @@ except ImportError:
|
|||
RecentlyUsedContainer = urllib3._collections.RecentlyUsedContainer
|
||||
|
||||
|
||||
def create_paramiko_client(base_url):
|
||||
logging.getLogger("paramiko").setLevel(logging.WARNING)
|
||||
ssh_client = paramiko.SSHClient()
|
||||
base_url = six.moves.urllib_parse.urlparse(base_url)
|
||||
ssh_params = {
|
||||
"hostname": base_url.hostname,
|
||||
"port": base_url.port,
|
||||
"username": base_url.username
|
||||
}
|
||||
ssh_config_file = os.path.expanduser("~/.ssh/config")
|
||||
if os.path.exists(ssh_config_file):
|
||||
conf = paramiko.SSHConfig()
|
||||
with open(ssh_config_file) as f:
|
||||
conf.parse(f)
|
||||
host_config = conf.lookup(base_url.hostname)
|
||||
ssh_conf = host_config
|
||||
if 'proxycommand' in host_config:
|
||||
ssh_params["sock"] = paramiko.ProxyCommand(
|
||||
ssh_conf['proxycommand']
|
||||
)
|
||||
if 'hostname' in host_config:
|
||||
ssh_params['hostname'] = host_config['hostname']
|
||||
if 'identityfile' in host_config:
|
||||
ssh_params['key_filename'] = host_config['identityfile']
|
||||
if base_url.port is None and 'port' in host_config:
|
||||
ssh_params['port'] = ssh_conf['port']
|
||||
if base_url.username is None and 'user' in host_config:
|
||||
ssh_params['username'] = ssh_conf['user']
|
||||
|
||||
ssh_client.load_system_host_keys()
|
||||
ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
return ssh_client, ssh_params
|
||||
|
||||
|
||||
class SSHSocket(socket.socket):
|
||||
def __init__(self, host):
|
||||
super(SSHSocket, self).__init__(
|
||||
socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.host = host
|
||||
self.port = None
|
||||
self.user = None
|
||||
if ':' in host:
|
||||
self.host, self.port = host.split(':')
|
||||
if '@' in self.host:
|
||||
self.user, self.host = host.split('@')
|
||||
|
||||
self.proc = None
|
||||
|
||||
def connect(self, **kwargs):
|
||||
port = '' if not self.port else '-p {}'.format(self.port)
|
||||
args = [
|
||||
'ssh',
|
||||
'-q',
|
||||
self.host,
|
||||
port,
|
||||
'docker system dial-stdio'
|
||||
]
|
||||
args = ['ssh']
|
||||
if self.user:
|
||||
args = args + ['-l', self.user]
|
||||
|
||||
if self.port:
|
||||
args = args + ['-p', self.port]
|
||||
|
||||
args = args + ['--', self.host, 'docker system dial-stdio']
|
||||
|
||||
preexec_func = None
|
||||
if not constants.IS_WINDOWS_PLATFORM:
|
||||
def f():
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
preexec_func = f
|
||||
|
||||
self.proc = subprocess.Popen(
|
||||
' '.join(args),
|
||||
env=os.environ,
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE)
|
||||
stdin=subprocess.PIPE,
|
||||
preexec_fn=preexec_func)
|
||||
|
||||
def _write(self, data):
|
||||
if not self.proc or self.proc.stdin.closed:
|
||||
|
@ -96,17 +75,18 @@ class SSHSocket(socket.socket):
|
|||
def send(self, data):
|
||||
return self._write(data)
|
||||
|
||||
def recv(self):
|
||||
def recv(self, n):
|
||||
if not self.proc:
|
||||
raise Exception('SSH subprocess not initiated.'
|
||||
'connect() must be called first.')
|
||||
return self.proc.stdout.read()
|
||||
return self.proc.stdout.read(n)
|
||||
|
||||
def makefile(self, mode):
|
||||
if not self.proc or self.proc.stdout.closed:
|
||||
buf = io.BytesIO()
|
||||
buf.write(b'\n\n')
|
||||
return buf
|
||||
if not self.proc:
|
||||
self.connect()
|
||||
if six.PY3:
|
||||
self.proc.stdout.channel = self
|
||||
|
||||
return self.proc.stdout
|
||||
|
||||
def close(self):
|
||||
|
@ -124,7 +104,7 @@ class SSHConnection(httplib.HTTPConnection, object):
|
|||
)
|
||||
self.ssh_transport = ssh_transport
|
||||
self.timeout = timeout
|
||||
self.host = host
|
||||
self.ssh_host = host
|
||||
|
||||
def connect(self):
|
||||
if self.ssh_transport:
|
||||
|
@ -132,7 +112,7 @@ class SSHConnection(httplib.HTTPConnection, object):
|
|||
sock.settimeout(self.timeout)
|
||||
sock.exec_command('docker system dial-stdio')
|
||||
else:
|
||||
sock = SSHSocket(self.host)
|
||||
sock = SSHSocket(self.ssh_host)
|
||||
sock.settimeout(self.timeout)
|
||||
sock.connect()
|
||||
|
||||
|
@ -147,16 +127,13 @@ class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
|
|||
'localhost', timeout=timeout, maxsize=maxsize
|
||||
)
|
||||
self.ssh_transport = None
|
||||
self.timeout = timeout
|
||||
if ssh_client:
|
||||
self.ssh_transport = ssh_client.get_transport()
|
||||
self.timeout = timeout
|
||||
self.host = host
|
||||
self.port = None
|
||||
if ':' in host:
|
||||
self.host, self.port = host.split(':')
|
||||
self.ssh_host = host
|
||||
|
||||
def _new_conn(self):
|
||||
return SSHConnection(self.ssh_transport, self.timeout, self.host)
|
||||
return SSHConnection(self.ssh_transport, self.timeout, self.ssh_host)
|
||||
|
||||
# When re-using connections, urllib3 calls fileno() on our
|
||||
# SSH channel instance, quickly overloading our fd limit. To avoid this,
|
||||
|
@ -184,29 +161,71 @@ class SSHConnectionPool(urllib3.connectionpool.HTTPConnectionPool):
|
|||
class SSHHTTPAdapter(BaseHTTPAdapter):
|
||||
|
||||
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + [
|
||||
'pools', 'timeout', 'ssh_client', 'ssh_params'
|
||||
'pools', 'timeout', 'ssh_client', 'ssh_params', 'max_pool_size'
|
||||
]
|
||||
|
||||
def __init__(self, base_url, timeout=60,
|
||||
pool_connections=constants.DEFAULT_NUM_POOLS,
|
||||
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE,
|
||||
shell_out=True):
|
||||
self.ssh_client = None
|
||||
if not shell_out:
|
||||
self.ssh_client, self.ssh_params = create_paramiko_client(base_url)
|
||||
self._create_paramiko_client(base_url)
|
||||
self._connect()
|
||||
base_url = base_url.lstrip('ssh://')
|
||||
self.host = base_url
|
||||
|
||||
self.ssh_host = base_url
|
||||
if base_url.startswith('ssh://'):
|
||||
self.ssh_host = base_url[len('ssh://'):]
|
||||
|
||||
self.timeout = timeout
|
||||
self.max_pool_size = max_pool_size
|
||||
self.pools = RecentlyUsedContainer(
|
||||
pool_connections, dispose_func=lambda p: p.close()
|
||||
)
|
||||
super(SSHHTTPAdapter, self).__init__()
|
||||
|
||||
def _create_paramiko_client(self, base_url):
|
||||
logging.getLogger("paramiko").setLevel(logging.WARNING)
|
||||
self.ssh_client = paramiko.SSHClient()
|
||||
base_url = six.moves.urllib_parse.urlparse(base_url)
|
||||
self.ssh_params = {
|
||||
"hostname": base_url.hostname,
|
||||
"port": base_url.port,
|
||||
"username": base_url.username
|
||||
}
|
||||
ssh_config_file = os.path.expanduser("~/.ssh/config")
|
||||
if os.path.exists(ssh_config_file):
|
||||
conf = paramiko.SSHConfig()
|
||||
with open(ssh_config_file) as f:
|
||||
conf.parse(f)
|
||||
host_config = conf.lookup(base_url.hostname)
|
||||
self.ssh_conf = host_config
|
||||
if 'proxycommand' in host_config:
|
||||
self.ssh_params["sock"] = paramiko.ProxyCommand(
|
||||
self.ssh_conf['proxycommand']
|
||||
)
|
||||
if 'hostname' in host_config:
|
||||
self.ssh_params['hostname'] = host_config['hostname']
|
||||
if base_url.port is None and 'port' in host_config:
|
||||
self.ssh_params['port'] = self.ssh_conf['port']
|
||||
if base_url.username is None and 'user' in host_config:
|
||||
self.ssh_params['username'] = self.ssh_conf['user']
|
||||
|
||||
self.ssh_client.load_system_host_keys()
|
||||
self.ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())
|
||||
|
||||
def _connect(self):
|
||||
if self.ssh_client:
|
||||
self.ssh_client.connect(**self.ssh_params)
|
||||
|
||||
def get_connection(self, url, proxies=None):
|
||||
if not self.ssh_client:
|
||||
return SSHConnectionPool(
|
||||
ssh_client=self.ssh_client,
|
||||
timeout=self.timeout,
|
||||
maxsize=self.max_pool_size,
|
||||
host=self.ssh_host
|
||||
)
|
||||
with self.pools.lock:
|
||||
pool = self.pools.get(url)
|
||||
if pool:
|
||||
|
@ -219,7 +238,8 @@ class SSHHTTPAdapter(BaseHTTPAdapter):
|
|||
pool = SSHConnectionPool(
|
||||
ssh_client=self.ssh_client,
|
||||
timeout=self.timeout,
|
||||
host=self.host
|
||||
maxsize=self.max_pool_size,
|
||||
host=self.ssh_host
|
||||
)
|
||||
self.pools[url] = pool
|
||||
|
||||
|
|
|
@ -74,15 +74,18 @@ class UnixHTTPAdapter(BaseHTTPAdapter):
|
|||
|
||||
__attrs__ = requests.adapters.HTTPAdapter.__attrs__ + ['pools',
|
||||
'socket_path',
|
||||
'timeout']
|
||||
'timeout',
|
||||
'max_pool_size']
|
||||
|
||||
def __init__(self, socket_url, timeout=60,
|
||||
pool_connections=constants.DEFAULT_NUM_POOLS):
|
||||
pool_connections=constants.DEFAULT_NUM_POOLS,
|
||||
max_pool_size=constants.DEFAULT_MAX_POOL_SIZE):
|
||||
socket_path = socket_url.replace('http+unix://', '')
|
||||
if not socket_path.startswith('/'):
|
||||
socket_path = '/' + socket_path
|
||||
self.socket_path = socket_path
|
||||
self.timeout = timeout
|
||||
self.max_pool_size = max_pool_size
|
||||
self.pools = RecentlyUsedContainer(
|
||||
pool_connections, dispose_func=lambda p: p.close()
|
||||
)
|
||||
|
@ -95,7 +98,8 @@ class UnixHTTPAdapter(BaseHTTPAdapter):
|
|||
return pool
|
||||
|
||||
pool = UnixHTTPConnectionPool(
|
||||
url, self.socket_path, self.timeout
|
||||
url, self.socket_path, self.timeout,
|
||||
maxsize=self.max_pool_size
|
||||
)
|
||||
self.pools[url] = pool
|
||||
|
||||
|
|
|
@ -334,10 +334,11 @@ class HostConfig(dict):
|
|||
if dns_search:
|
||||
self['DnsSearch'] = dns_search
|
||||
|
||||
if network_mode:
|
||||
self['NetworkMode'] = network_mode
|
||||
elif network_mode is None:
|
||||
self['NetworkMode'] = 'default'
|
||||
if network_mode == 'host' and port_bindings:
|
||||
raise host_config_incompatible_error(
|
||||
'network_mode', 'host', 'port_bindings'
|
||||
)
|
||||
self['NetworkMode'] = network_mode or 'default'
|
||||
|
||||
if restart_policy:
|
||||
if not isinstance(restart_policy, dict):
|
||||
|
@ -664,6 +665,13 @@ def host_config_value_error(param, param_value):
|
|||
return ValueError(error_msg.format(param, param_value))
|
||||
|
||||
|
||||
def host_config_incompatible_error(param, param_value, incompatible_param):
|
||||
error_msg = '\"{1}\" {0} is incompatible with {2}'
|
||||
return errors.InvalidArgument(
|
||||
error_msg.format(param, param_value, incompatible_param)
|
||||
)
|
||||
|
||||
|
||||
class ContainerConfig(dict):
|
||||
def __init__(
|
||||
self, version, image, command, hostname=None, user=None, detach=False,
|
||||
|
|
|
@ -105,8 +105,9 @@ def create_archive(root, files=None, fileobj=None, gzip=False,
|
|||
|
||||
for name, contents in extra_files:
|
||||
info = tarfile.TarInfo(name)
|
||||
info.size = len(contents)
|
||||
t.addfile(info, io.BytesIO(contents.encode('utf-8')))
|
||||
contents_encoded = contents.encode('utf-8')
|
||||
info.size = len(contents_encoded)
|
||||
t.addfile(info, io.BytesIO(contents_encoded))
|
||||
|
||||
t.close()
|
||||
fileobj.seek(0)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
version = "4.4.0-dev"
|
||||
version = "4.4.2"
|
||||
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])
|
||||
|
|
|
@ -1,6 +1,47 @@
|
|||
Change log
|
||||
==========
|
||||
|
||||
4.4.2
|
||||
-----
|
||||
|
||||
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/71?closed=1)
|
||||
|
||||
### Bugfixes
|
||||
- Fix SSH connection bug where the hostname was incorrectly trimmed and the error was hidden
|
||||
- Fix docs example
|
||||
|
||||
### Miscellaneous
|
||||
- Add Python3.8 and 3.9 in setup.py classifier list
|
||||
|
||||
|
||||
4.4.1
|
||||
-----
|
||||
|
||||
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/69?closed=1)
|
||||
|
||||
### Bugfixes
|
||||
- Avoid setting unsuported parameter for subprocess.Popen on Windows
|
||||
- Replace use of deprecated "filter" argument on ""docker/api/image"
|
||||
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/67?closed=1)
|
||||
|
||||
### Features
|
||||
- Add an alternative SSH connection to the paramiko one, based on shelling out to the SSh client. Similar to the behaviour of Docker cli
|
||||
- Default image tag to `latest` on `pull`
|
||||
|
||||
### Bugfixes
|
||||
- Fix plugin model upgrade
|
||||
- Fix examples URL in ulimits
|
||||
|
||||
### Miscellaneous
|
||||
- Improve exception messages for server and client errors
|
||||
- Bump cryptography from 2.3 to 3.2
|
||||
|
||||
|
||||
4.3.1
|
||||
-----
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ You can stream logs:
|
|||
.. code-block:: python
|
||||
|
||||
>>> for line in container.logs(stream=True):
|
||||
... print line.strip()
|
||||
... print(line.strip())
|
||||
Reticulating spline 2...
|
||||
Reticulating spline 3...
|
||||
...
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
appdirs==1.4.3
|
||||
asn1crypto==0.22.0
|
||||
backports.ssl-match-hostname==3.5.0.1
|
||||
cffi==1.10.0
|
||||
cryptography==2.3
|
||||
cffi==1.14.4
|
||||
cryptography==3.2
|
||||
enum34==1.1.6
|
||||
idna==2.5
|
||||
ipaddress==1.0.18
|
||||
|
|
2
setup.py
2
setup.py
|
@ -84,6 +84,8 @@ setup(
|
|||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Utilities',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
|
|
|
@ -10,7 +10,7 @@ RUN apk add --no-cache \
|
|||
RUN ssh-keygen -A
|
||||
|
||||
# copy the test SSH config
|
||||
RUN echo "IgnoreUserKnownHosts yes" >> /etc/ssh/sshd_config && \
|
||||
RUN echo "IgnoreUserKnownHosts yes" > /etc/ssh/sshd_config && \
|
||||
echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config && \
|
||||
echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
|
||||
|
||||
|
|
|
@ -26,7 +26,18 @@ class ImageTest(BaseAPIClientTest):
|
|||
fake_request.assert_called_with(
|
||||
'GET',
|
||||
url_prefix + 'images/json',
|
||||
params={'filter': None, 'only_ids': 0, 'all': 1},
|
||||
params={'only_ids': 0, 'all': 1},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
def test_images_name(self):
|
||||
self.client.images('foo:bar')
|
||||
|
||||
fake_request.assert_called_with(
|
||||
'GET',
|
||||
url_prefix + 'images/json',
|
||||
params={'only_ids': 0, 'all': 0,
|
||||
'filters': '{"reference": ["foo:bar"]}'},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
|
@ -36,7 +47,7 @@ class ImageTest(BaseAPIClientTest):
|
|||
fake_request.assert_called_with(
|
||||
'GET',
|
||||
url_prefix + 'images/json',
|
||||
params={'filter': None, 'only_ids': 1, 'all': 1},
|
||||
params={'only_ids': 1, 'all': 1},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
|
@ -46,7 +57,7 @@ class ImageTest(BaseAPIClientTest):
|
|||
fake_request.assert_called_with(
|
||||
'GET',
|
||||
url_prefix + 'images/json',
|
||||
params={'filter': None, 'only_ids': 1, 'all': 0},
|
||||
params={'only_ids': 1, 'all': 0},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
|
@ -56,7 +67,7 @@ class ImageTest(BaseAPIClientTest):
|
|||
fake_request.assert_called_with(
|
||||
'GET',
|
||||
url_prefix + 'images/json',
|
||||
params={'filter': None, 'only_ids': 0, 'all': 0,
|
||||
params={'only_ids': 0, 'all': 0,
|
||||
'filters': '{"dangling": ["true"]}'},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
|
|
@ -5,7 +5,9 @@ import unittest
|
|||
import docker
|
||||
import pytest
|
||||
from docker.constants import (
|
||||
DEFAULT_DOCKER_API_VERSION, DEFAULT_TIMEOUT_SECONDS)
|
||||
DEFAULT_DOCKER_API_VERSION, DEFAULT_TIMEOUT_SECONDS,
|
||||
DEFAULT_MAX_POOL_SIZE, IS_WINDOWS_PLATFORM
|
||||
)
|
||||
from docker.utils import kwargs_from_env
|
||||
|
||||
from . import fake_api
|
||||
|
@ -15,8 +17,8 @@ try:
|
|||
except ImportError:
|
||||
import mock
|
||||
|
||||
|
||||
TEST_CERT_DIR = os.path.join(os.path.dirname(__file__), 'testdata/certs')
|
||||
POOL_SIZE = 20
|
||||
|
||||
|
||||
class ClientTest(unittest.TestCase):
|
||||
|
@ -76,6 +78,84 @@ class ClientTest(unittest.TestCase):
|
|||
assert "'ContainerCollection' object is not callable" in s
|
||||
assert "docker.APIClient" in s
|
||||
|
||||
@pytest.mark.skipif(
|
||||
IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux'
|
||||
)
|
||||
@mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool")
|
||||
def test_default_pool_size_unix(self, mock_obj):
|
||||
client = docker.DockerClient(
|
||||
version=DEFAULT_DOCKER_API_VERSION
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
base_url = "{base_url}/v{version}/_ping".format(
|
||||
base_url=client.api.base_url,
|
||||
version=client.api._version
|
||||
)
|
||||
|
||||
mock_obj.assert_called_once_with(base_url,
|
||||
"/var/run/docker.sock",
|
||||
60,
|
||||
maxsize=DEFAULT_MAX_POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows'
|
||||
)
|
||||
@mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool")
|
||||
def test_default_pool_size_win(self, mock_obj):
|
||||
client = docker.DockerClient(
|
||||
version=DEFAULT_DOCKER_API_VERSION
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
mock_obj.assert_called_once_with("//./pipe/docker_engine",
|
||||
60,
|
||||
maxsize=DEFAULT_MAX_POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux'
|
||||
)
|
||||
@mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool")
|
||||
def test_pool_size_unix(self, mock_obj):
|
||||
client = docker.DockerClient(
|
||||
version=DEFAULT_DOCKER_API_VERSION,
|
||||
max_pool_size=POOL_SIZE
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
base_url = "{base_url}/v{version}/_ping".format(
|
||||
base_url=client.api.base_url,
|
||||
version=client.api._version
|
||||
)
|
||||
|
||||
mock_obj.assert_called_once_with(base_url,
|
||||
"/var/run/docker.sock",
|
||||
60,
|
||||
maxsize=POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows'
|
||||
)
|
||||
@mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool")
|
||||
def test_pool_size_win(self, mock_obj):
|
||||
client = docker.DockerClient(
|
||||
version=DEFAULT_DOCKER_API_VERSION,
|
||||
max_pool_size=POOL_SIZE
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
mock_obj.assert_called_once_with("//./pipe/docker_engine",
|
||||
60,
|
||||
maxsize=POOL_SIZE
|
||||
)
|
||||
|
||||
|
||||
class FromEnvTest(unittest.TestCase):
|
||||
|
||||
|
@ -112,3 +192,77 @@ class FromEnvTest(unittest.TestCase):
|
|||
client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION)
|
||||
|
||||
assert client.api.timeout == DEFAULT_TIMEOUT_SECONDS
|
||||
|
||||
@pytest.mark.skipif(
|
||||
IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux'
|
||||
)
|
||||
@mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool")
|
||||
def test_default_pool_size_from_env_unix(self, mock_obj):
|
||||
client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
base_url = "{base_url}/v{version}/_ping".format(
|
||||
base_url=client.api.base_url,
|
||||
version=client.api._version
|
||||
)
|
||||
|
||||
mock_obj.assert_called_once_with(base_url,
|
||||
"/var/run/docker.sock",
|
||||
60,
|
||||
maxsize=DEFAULT_MAX_POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows'
|
||||
)
|
||||
@mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool")
|
||||
def test_default_pool_size_from_env_win(self, mock_obj):
|
||||
client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
mock_obj.assert_called_once_with("//./pipe/docker_engine",
|
||||
60,
|
||||
maxsize=DEFAULT_MAX_POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
IS_WINDOWS_PLATFORM, reason='Unix Connection Pool only on Linux'
|
||||
)
|
||||
@mock.patch("docker.transport.unixconn.UnixHTTPConnectionPool")
|
||||
def test_pool_size_from_env_unix(self, mock_obj):
|
||||
client = docker.from_env(
|
||||
version=DEFAULT_DOCKER_API_VERSION,
|
||||
max_pool_size=POOL_SIZE
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
base_url = "{base_url}/v{version}/_ping".format(
|
||||
base_url=client.api.base_url,
|
||||
version=client.api._version
|
||||
)
|
||||
|
||||
mock_obj.assert_called_once_with(base_url,
|
||||
"/var/run/docker.sock",
|
||||
60,
|
||||
maxsize=POOL_SIZE
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not IS_WINDOWS_PLATFORM, reason='Npipe Connection Pool only on Windows'
|
||||
)
|
||||
@mock.patch("docker.transport.npipeconn.NpipeHTTPConnectionPool")
|
||||
def test_pool_size_from_env_win(self, mock_obj):
|
||||
client = docker.from_env(
|
||||
version=DEFAULT_DOCKER_API_VERSION,
|
||||
max_pool_size=POOL_SIZE
|
||||
)
|
||||
mock_obj.return_value.urlopen.return_value.status = 200
|
||||
client.ping()
|
||||
|
||||
mock_obj.assert_called_once_with("//./pipe/docker_engine",
|
||||
60,
|
||||
maxsize=POOL_SIZE
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue