mirror of https://github.com/docker/docker-py.git
commit
f70545e89a
|
@ -302,7 +302,8 @@ class BuildApiMixin(object):
|
||||||
# credentials/native_store.go#L68-L83
|
# credentials/native_store.go#L68-L83
|
||||||
for registry in self._auth_configs.get('auths', {}).keys():
|
for registry in self._auth_configs.get('auths', {}).keys():
|
||||||
auth_data[registry] = auth.resolve_authconfig(
|
auth_data[registry] = auth.resolve_authconfig(
|
||||||
self._auth_configs, registry
|
self._auth_configs, registry,
|
||||||
|
credstore_env=self.credstore_env,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
auth_data = self._auth_configs.get('auths', {}).copy()
|
auth_data = self._auth_configs.get('auths', {}).copy()
|
||||||
|
@ -341,4 +342,9 @@ def process_dockerfile(dockerfile, path):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Dockerfile is inside the context - return path relative to context root
|
# Dockerfile is inside the context - return path relative to context root
|
||||||
return (os.path.relpath(abs_dockerfile, path), None)
|
if dockerfile == abs_dockerfile:
|
||||||
|
# Only calculate relpath if necessary to avoid errors
|
||||||
|
# on Windows client -> Linux Docker
|
||||||
|
# see https://github.com/docker/compose/issues/5969
|
||||||
|
dockerfile = os.path.relpath(abs_dockerfile, path)
|
||||||
|
return (dockerfile, None)
|
||||||
|
|
|
@ -83,6 +83,8 @@ class APIClient(
|
||||||
:py:class:`~docker.tls.TLSConfig` object to use custom
|
:py:class:`~docker.tls.TLSConfig` object to use custom
|
||||||
configuration.
|
configuration.
|
||||||
user_agent (str): Set a custom user agent for requests to the server.
|
user_agent (str): Set a custom user agent for requests to the server.
|
||||||
|
credstore_env (dict): Override environment variables when calling the
|
||||||
|
credential store process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
|
__attrs__ = requests.Session.__attrs__ + ['_auth_configs',
|
||||||
|
@ -93,7 +95,8 @@ class APIClient(
|
||||||
|
|
||||||
def __init__(self, base_url=None, version=None,
|
def __init__(self, base_url=None, version=None,
|
||||||
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
|
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False,
|
||||||
user_agent=DEFAULT_USER_AGENT, num_pools=DEFAULT_NUM_POOLS):
|
user_agent=DEFAULT_USER_AGENT, num_pools=DEFAULT_NUM_POOLS,
|
||||||
|
credstore_env=None):
|
||||||
super(APIClient, self).__init__()
|
super(APIClient, self).__init__()
|
||||||
|
|
||||||
if tls and not base_url:
|
if tls and not base_url:
|
||||||
|
@ -109,6 +112,7 @@ class APIClient(
|
||||||
self._auth_configs = auth.load_config(
|
self._auth_configs = auth.load_config(
|
||||||
config_dict=self._general_configs
|
config_dict=self._general_configs
|
||||||
)
|
)
|
||||||
|
self.credstore_env = credstore_env
|
||||||
|
|
||||||
base_url = utils.parse_host(
|
base_url = utils.parse_host(
|
||||||
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)
|
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)
|
||||||
|
|
|
@ -128,7 +128,9 @@ class DaemonApiMixin(object):
|
||||||
elif not self._auth_configs:
|
elif not self._auth_configs:
|
||||||
self._auth_configs = auth.load_config()
|
self._auth_configs = auth.load_config()
|
||||||
|
|
||||||
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
|
authcfg = auth.resolve_authconfig(
|
||||||
|
self._auth_configs, registry, credstore_env=self.credstore_env,
|
||||||
|
)
|
||||||
# If we found an existing auth config for this registry and username
|
# If we found an existing auth config for this registry and username
|
||||||
# combination, we can return it immediately unless reauth is requested.
|
# combination, we can return it immediately unless reauth is requested.
|
||||||
if authcfg and authcfg.get('username', None) == username \
|
if authcfg and authcfg.get('username', None) == username \
|
||||||
|
|
|
@ -44,7 +44,10 @@ class PluginApiMixin(object):
|
||||||
"""
|
"""
|
||||||
url = self._url('/plugins/create')
|
url = self._url('/plugins/create')
|
||||||
|
|
||||||
with utils.create_archive(root=plugin_data_dir, gzip=gzip) as archv:
|
with utils.create_archive(
|
||||||
|
root=plugin_data_dir, gzip=gzip,
|
||||||
|
files=set(utils.build.walk(plugin_data_dir, []))
|
||||||
|
) as archv:
|
||||||
res = self._post(url, params={'name': name}, data=archv)
|
res = self._post(url, params={'name': name}, data=archv)
|
||||||
self._raise_for_status(res)
|
self._raise_for_status(res)
|
||||||
return True
|
return True
|
||||||
|
@ -167,8 +170,16 @@ class PluginApiMixin(object):
|
||||||
'remote': name,
|
'remote': name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headers = {}
|
||||||
|
registry, repo_name = auth.resolve_repository_name(name)
|
||||||
|
header = auth.get_config_header(self, registry)
|
||||||
|
if header:
|
||||||
|
headers['X-Registry-Auth'] = header
|
||||||
|
|
||||||
url = self._url('/plugins/privileges')
|
url = self._url('/plugins/privileges')
|
||||||
return self._result(self._get(url, params=params), True)
|
return self._result(
|
||||||
|
self._get(url, params=params, headers=headers), True
|
||||||
|
)
|
||||||
|
|
||||||
@utils.minimum_version('1.25')
|
@utils.minimum_version('1.25')
|
||||||
@utils.check_resource('name')
|
@utils.check_resource('name')
|
||||||
|
|
|
@ -44,7 +44,9 @@ def get_config_header(client, registry):
|
||||||
"No auth config in memory - loading from filesystem"
|
"No auth config in memory - loading from filesystem"
|
||||||
)
|
)
|
||||||
client._auth_configs = load_config()
|
client._auth_configs = load_config()
|
||||||
authcfg = resolve_authconfig(client._auth_configs, registry)
|
authcfg = resolve_authconfig(
|
||||||
|
client._auth_configs, registry, credstore_env=client.credstore_env
|
||||||
|
)
|
||||||
# Do not fail here if no authentication exists for this
|
# Do not fail here if no authentication exists for this
|
||||||
# specific registry as we can have a readonly pull. Just
|
# specific registry as we can have a readonly pull. Just
|
||||||
# put the header if we can.
|
# put the header if we can.
|
||||||
|
@ -76,7 +78,7 @@ def get_credential_store(authconfig, registry):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def resolve_authconfig(authconfig, registry=None):
|
def resolve_authconfig(authconfig, registry=None, credstore_env=None):
|
||||||
"""
|
"""
|
||||||
Returns the authentication data from the given auth configuration for a
|
Returns the authentication data from the given auth configuration for a
|
||||||
specific registry. As with the Docker client, legacy entries in the config
|
specific registry. As with the Docker client, legacy entries in the config
|
||||||
|
@ -91,7 +93,7 @@ def resolve_authconfig(authconfig, registry=None):
|
||||||
'Using credentials store "{0}"'.format(store_name)
|
'Using credentials store "{0}"'.format(store_name)
|
||||||
)
|
)
|
||||||
cfg = _resolve_authconfig_credstore(
|
cfg = _resolve_authconfig_credstore(
|
||||||
authconfig, registry, store_name
|
authconfig, registry, store_name, env=credstore_env
|
||||||
)
|
)
|
||||||
if cfg is not None:
|
if cfg is not None:
|
||||||
return cfg
|
return cfg
|
||||||
|
@ -115,13 +117,14 @@ def resolve_authconfig(authconfig, registry=None):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _resolve_authconfig_credstore(authconfig, registry, credstore_name):
|
def _resolve_authconfig_credstore(authconfig, registry, credstore_name,
|
||||||
|
env=None):
|
||||||
if not registry or registry == INDEX_NAME:
|
if not registry or registry == INDEX_NAME:
|
||||||
# The ecosystem is a little schizophrenic with index.docker.io VS
|
# The ecosystem is a little schizophrenic with index.docker.io VS
|
||||||
# docker.io - in that case, it seems the full URL is necessary.
|
# docker.io - in that case, it seems the full URL is necessary.
|
||||||
registry = INDEX_URL
|
registry = INDEX_URL
|
||||||
log.debug("Looking for auth entry for {0}".format(repr(registry)))
|
log.debug("Looking for auth entry for {0}".format(repr(registry)))
|
||||||
store = dockerpycreds.Store(credstore_name)
|
store = dockerpycreds.Store(credstore_name, environment=env)
|
||||||
try:
|
try:
|
||||||
data = store.get(registry)
|
data = store.get(registry)
|
||||||
res = {
|
res = {
|
||||||
|
|
|
@ -33,6 +33,8 @@ class DockerClient(object):
|
||||||
:py:class:`~docker.tls.TLSConfig` object to use custom
|
:py:class:`~docker.tls.TLSConfig` object to use custom
|
||||||
configuration.
|
configuration.
|
||||||
user_agent (str): Set a custom user agent for requests to the server.
|
user_agent (str): Set a custom user agent for requests to the server.
|
||||||
|
credstore_env (dict): Override environment variables when calling the
|
||||||
|
credential store process.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.api = APIClient(*args, **kwargs)
|
self.api = APIClient(*args, **kwargs)
|
||||||
|
@ -66,6 +68,8 @@ class DockerClient(object):
|
||||||
assert_hostname (bool): Verify the hostname of the server.
|
assert_hostname (bool): Verify the hostname of the server.
|
||||||
environment (dict): The environment to read environment variables
|
environment (dict): The environment to read environment variables
|
||||||
from. Default: the value of ``os.environ``
|
from. Default: the value of ``os.environ``
|
||||||
|
credstore_env (dict): Override environment variables when calling
|
||||||
|
the credential store process.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
@ -77,8 +81,9 @@ class DockerClient(object):
|
||||||
"""
|
"""
|
||||||
timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
|
timeout = kwargs.pop('timeout', DEFAULT_TIMEOUT_SECONDS)
|
||||||
version = kwargs.pop('version', None)
|
version = kwargs.pop('version', None)
|
||||||
return cls(timeout=timeout, version=version,
|
return cls(
|
||||||
**kwargs_from_env(**kwargs))
|
timeout=timeout, version=version, **kwargs_from_env(**kwargs)
|
||||||
|
)
|
||||||
|
|
||||||
# Resources
|
# Resources
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -211,5 +211,5 @@ class NetworkCollection(Collection):
|
||||||
return networks
|
return networks
|
||||||
|
|
||||||
def prune(self, filters=None):
|
def prune(self, filters=None):
|
||||||
self.client.api.prune_networks(filters=filters)
|
return self.client.api.prune_networks(filters=filters)
|
||||||
prune.__doc__ = APIClient.prune_networks.__doc__
|
prune.__doc__ = APIClient.prune_networks.__doc__
|
||||||
|
|
|
@ -126,7 +126,7 @@ class Service(Model):
|
||||||
|
|
||||||
service_mode = ServiceMode('replicated', replicas)
|
service_mode = ServiceMode('replicated', replicas)
|
||||||
return self.client.api.update_service(self.id, self.version,
|
return self.client.api.update_service(self.id, self.version,
|
||||||
service_mode,
|
mode=service_mode,
|
||||||
fetch_current_spec=True)
|
fetch_current_spec=True)
|
||||||
|
|
||||||
def force_update(self):
|
def force_update(self):
|
||||||
|
@ -276,7 +276,7 @@ CONTAINER_SPEC_KWARGS = [
|
||||||
'labels',
|
'labels',
|
||||||
'mounts',
|
'mounts',
|
||||||
'open_stdin',
|
'open_stdin',
|
||||||
'privileges'
|
'privileges',
|
||||||
'read_only',
|
'read_only',
|
||||||
'secrets',
|
'secrets',
|
||||||
'stop_grace_period',
|
'stop_grace_period',
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import six
|
import six
|
||||||
import requests.adapters
|
import requests.adapters
|
||||||
import socket
|
import socket
|
||||||
|
from six.moves import http_client as httplib
|
||||||
|
|
||||||
from .. import constants
|
from .. import constants
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
import http.client as httplib
|
|
||||||
else:
|
|
||||||
import httplib
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import requests.packages.urllib3 as urllib3
|
import requests.packages.urllib3 as urllib3
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -57,6 +57,8 @@ class CancellableStream(object):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
sock = sock_fp._sock
|
sock = sock_fp._sock
|
||||||
|
if isinstance(sock, urllib3.contrib.pyopenssl.WrappedSocket):
|
||||||
|
sock = sock.socket
|
||||||
|
|
||||||
sock.shutdown(socket.SHUT_RDWR)
|
sock.shutdown(socket.SHUT_RDWR)
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
|
@ -82,7 +82,7 @@ class ContainerSpec(dict):
|
||||||
args (:py:class:`list`): Arguments to the command.
|
args (:py:class:`list`): Arguments to the command.
|
||||||
hostname (string): The hostname to set on the container.
|
hostname (string): The hostname to set on the container.
|
||||||
env (dict): Environment variables.
|
env (dict): Environment variables.
|
||||||
dir (string): The working directory for commands to run in.
|
workdir (string): The working directory for commands to run in.
|
||||||
user (string): The user inside the container.
|
user (string): The user inside the container.
|
||||||
labels (dict): A map of labels to associate with the service.
|
labels (dict): A map of labels to associate with the service.
|
||||||
mounts (:py:class:`list`): A list of specifications for mounts to be
|
mounts (:py:class:`list`): A list of specifications for mounts to be
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import errno
|
import errno
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
|
import socket as pysocket
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -28,6 +29,8 @@ def read(socket, n=4096):
|
||||||
try:
|
try:
|
||||||
if hasattr(socket, 'recv'):
|
if hasattr(socket, 'recv'):
|
||||||
return socket.recv(n)
|
return socket.recv(n)
|
||||||
|
if six.PY3 and isinstance(socket, getattr(pysocket, 'SocketIO')):
|
||||||
|
return socket.read(n)
|
||||||
return os.read(socket.fileno(), n)
|
return os.read(socket.fileno(), n)
|
||||||
except EnvironmentError as e:
|
except EnvironmentError as e:
|
||||||
if e.errno not in recoverable_errors:
|
if e.errno not in recoverable_errors:
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
version = "3.3.0"
|
version = "3.4.0"
|
||||||
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])
|
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])
|
||||||
|
|
|
@ -1,6 +1,31 @@
|
||||||
Change log
|
Change log
|
||||||
==========
|
==========
|
||||||
|
|
||||||
|
3.4.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/51?closed=1)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* The `APIClient` and `DockerClient` constructors now accept a `credstore_env`
|
||||||
|
parameter. When set, values in this dictionary are added to the environment
|
||||||
|
when executing the credential store process.
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
|
||||||
|
* `DockerClient.networks.prune` now properly returns the operation's result
|
||||||
|
* Fixed a bug that caused custom Dockerfile paths in a subfolder of the build
|
||||||
|
context to be invalidated, preventing these builds from working
|
||||||
|
* The `plugin_privileges` method can now be called for plugins requiring
|
||||||
|
authentication to access
|
||||||
|
* Fixed a bug that caused attempts to read a data stream over an unsecured TCP
|
||||||
|
socket to crash on Windows clients
|
||||||
|
* Fixed a bug where using the `read_only` parameter when creating a service using
|
||||||
|
the `DockerClient` was being ignored
|
||||||
|
* Fixed an issue where `Service.scale` would not properly update the service's
|
||||||
|
mode, causing the operation to fail silently
|
||||||
|
|
||||||
3.3.0
|
3.3.0
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ asn1crypto==0.22.0
|
||||||
backports.ssl-match-hostname==3.5.0.1
|
backports.ssl-match-hostname==3.5.0.1
|
||||||
cffi==1.10.0
|
cffi==1.10.0
|
||||||
cryptography==1.9
|
cryptography==1.9
|
||||||
docker-pycreds==0.2.3
|
docker-pycreds==0.3.0
|
||||||
enum34==1.1.6
|
enum34==1.1.6
|
||||||
idna==2.5
|
idna==2.5
|
||||||
ipaddress==1.0.18
|
ipaddress==1.0.18
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -13,7 +13,7 @@ requirements = [
|
||||||
'requests >= 2.14.2, != 2.18.0',
|
'requests >= 2.14.2, != 2.18.0',
|
||||||
'six >= 1.4.0',
|
'six >= 1.4.0',
|
||||||
'websocket-client >= 0.32.0',
|
'websocket-client >= 0.32.0',
|
||||||
'docker-pycreds >= 0.2.3'
|
'docker-pycreds >= 0.3.0'
|
||||||
]
|
]
|
||||||
|
|
||||||
extras_require = {
|
extras_require = {
|
||||||
|
|
|
@ -415,18 +415,20 @@ class BuildTest(BaseAPIIntegrationTest):
|
||||||
f.write('hello world')
|
f.write('hello world')
|
||||||
with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
|
with open(os.path.join(base_dir, '.dockerignore'), 'w') as f:
|
||||||
f.write('.dockerignore\n')
|
f.write('.dockerignore\n')
|
||||||
df = tempfile.NamedTemporaryFile()
|
df_dir = tempfile.mkdtemp()
|
||||||
self.addCleanup(df.close)
|
self.addCleanup(shutil.rmtree, df_dir)
|
||||||
df.write(('\n'.join([
|
df_name = os.path.join(df_dir, 'Dockerfile')
|
||||||
'FROM busybox',
|
with open(df_name, 'wb') as df:
|
||||||
'COPY . /src',
|
df.write(('\n'.join([
|
||||||
'WORKDIR /src',
|
'FROM busybox',
|
||||||
])).encode('utf-8'))
|
'COPY . /src',
|
||||||
df.flush()
|
'WORKDIR /src',
|
||||||
|
])).encode('utf-8'))
|
||||||
|
df.flush()
|
||||||
img_name = random_name()
|
img_name = random_name()
|
||||||
self.tmp_imgs.append(img_name)
|
self.tmp_imgs.append(img_name)
|
||||||
stream = self.client.build(
|
stream = self.client.build(
|
||||||
path=base_dir, dockerfile=df.name, tag=img_name,
|
path=base_dir, dockerfile=df_name, tag=img_name,
|
||||||
decode=True
|
decode=True
|
||||||
)
|
)
|
||||||
lines = []
|
lines = []
|
||||||
|
@ -472,6 +474,39 @@ class BuildTest(BaseAPIIntegrationTest):
|
||||||
[b'.', b'..', b'file.txt', b'custom.dockerfile']
|
[b'.', b'..', b'file.txt', b'custom.dockerfile']
|
||||||
) == sorted(lsdata)
|
) == sorted(lsdata)
|
||||||
|
|
||||||
|
def test_build_in_context_nested_dockerfile(self):
|
||||||
|
base_dir = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, base_dir)
|
||||||
|
with open(os.path.join(base_dir, 'file.txt'), 'w') as f:
|
||||||
|
f.write('hello world')
|
||||||
|
subdir = os.path.join(base_dir, 'hello', 'world')
|
||||||
|
os.makedirs(subdir)
|
||||||
|
with open(os.path.join(subdir, 'custom.dockerfile'), 'w') as df:
|
||||||
|
df.write('\n'.join([
|
||||||
|
'FROM busybox',
|
||||||
|
'COPY . /src',
|
||||||
|
'WORKDIR /src',
|
||||||
|
]))
|
||||||
|
img_name = random_name()
|
||||||
|
self.tmp_imgs.append(img_name)
|
||||||
|
stream = self.client.build(
|
||||||
|
path=base_dir, dockerfile='hello/world/custom.dockerfile',
|
||||||
|
tag=img_name, decode=True
|
||||||
|
)
|
||||||
|
lines = []
|
||||||
|
for chunk in stream:
|
||||||
|
lines.append(chunk)
|
||||||
|
assert 'Successfully tagged' in lines[-1]['stream']
|
||||||
|
|
||||||
|
ctnr = self.client.create_container(img_name, 'ls -a')
|
||||||
|
self.tmp_containers.append(ctnr)
|
||||||
|
self.client.start(ctnr)
|
||||||
|
lsdata = self.client.logs(ctnr).strip().split(b'\n')
|
||||||
|
assert len(lsdata) == 4
|
||||||
|
assert sorted(
|
||||||
|
[b'.', b'..', b'file.txt', b'hello']
|
||||||
|
) == sorted(lsdata)
|
||||||
|
|
||||||
def test_build_in_context_abs_dockerfile(self):
|
def test_build_in_context_abs_dockerfile(self):
|
||||||
base_dir = tempfile.mkdtemp()
|
base_dir = tempfile.mkdtemp()
|
||||||
self.addCleanup(shutil.rmtree, base_dir)
|
self.addCleanup(shutil.rmtree, base_dir)
|
||||||
|
|
|
@ -491,6 +491,9 @@ class CreateContainerTest(BaseAPIIntegrationTest):
|
||||||
assert rule in self.client.logs(ctnr).decode('utf-8')
|
assert rule in self.client.logs(ctnr).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(
|
||||||
|
IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform'
|
||||||
|
)
|
||||||
class VolumeBindTest(BaseAPIIntegrationTest):
|
class VolumeBindTest(BaseAPIIntegrationTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(VolumeBindTest, self).setUp()
|
super(VolumeBindTest, self).setUp()
|
||||||
|
@ -507,9 +510,6 @@ class VolumeBindTest(BaseAPIIntegrationTest):
|
||||||
['touch', os.path.join(self.mount_dest, self.filename)],
|
['touch', os.path.join(self.mount_dest, self.filename)],
|
||||||
)
|
)
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform'
|
|
||||||
)
|
|
||||||
def test_create_with_binds_rw(self):
|
def test_create_with_binds_rw(self):
|
||||||
|
|
||||||
container = self.run_with_volume(
|
container = self.run_with_volume(
|
||||||
|
@ -525,9 +525,6 @@ class VolumeBindTest(BaseAPIIntegrationTest):
|
||||||
inspect_data = self.client.inspect_container(container)
|
inspect_data = self.client.inspect_container(container)
|
||||||
self.check_container_data(inspect_data, True)
|
self.check_container_data(inspect_data, True)
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform'
|
|
||||||
)
|
|
||||||
def test_create_with_binds_ro(self):
|
def test_create_with_binds_ro(self):
|
||||||
self.run_with_volume(
|
self.run_with_volume(
|
||||||
False,
|
False,
|
||||||
|
@ -548,9 +545,6 @@ class VolumeBindTest(BaseAPIIntegrationTest):
|
||||||
inspect_data = self.client.inspect_container(container)
|
inspect_data = self.client.inspect_container(container)
|
||||||
self.check_container_data(inspect_data, False)
|
self.check_container_data(inspect_data, False)
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform'
|
|
||||||
)
|
|
||||||
@requires_api_version('1.30')
|
@requires_api_version('1.30')
|
||||||
def test_create_with_mounts(self):
|
def test_create_with_mounts(self):
|
||||||
mount = docker.types.Mount(
|
mount = docker.types.Mount(
|
||||||
|
@ -569,9 +563,6 @@ class VolumeBindTest(BaseAPIIntegrationTest):
|
||||||
inspect_data = self.client.inspect_container(container)
|
inspect_data = self.client.inspect_container(container)
|
||||||
self.check_container_data(inspect_data, True)
|
self.check_container_data(inspect_data, True)
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
|
||||||
IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform'
|
|
||||||
)
|
|
||||||
@requires_api_version('1.30')
|
@requires_api_version('1.30')
|
||||||
def test_create_with_mounts_ro(self):
|
def test_create_with_mounts_ro(self):
|
||||||
mount = docker.types.Mount(
|
mount = docker.types.Mount(
|
||||||
|
@ -1116,9 +1107,7 @@ class ContainerTopTest(BaseAPIIntegrationTest):
|
||||||
|
|
||||||
self.client.start(container)
|
self.client.start(container)
|
||||||
res = self.client.top(container)
|
res = self.client.top(container)
|
||||||
if IS_WINDOWS_PLATFORM:
|
if not IS_WINDOWS_PLATFORM:
|
||||||
assert res['Titles'] == ['PID', 'USER', 'TIME', 'COMMAND']
|
|
||||||
else:
|
|
||||||
assert res['Titles'] == [
|
assert res['Titles'] == [
|
||||||
'UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'
|
'UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'
|
||||||
]
|
]
|
||||||
|
|
|
@ -135,7 +135,7 @@ class PluginTest(BaseAPIIntegrationTest):
|
||||||
|
|
||||||
def test_create_plugin(self):
|
def test_create_plugin(self):
|
||||||
plugin_data_dir = os.path.join(
|
plugin_data_dir = os.path.join(
|
||||||
os.path.dirname(__file__), 'testdata/dummy-plugin'
|
os.path.dirname(__file__), os.path.join('testdata', 'dummy-plugin')
|
||||||
)
|
)
|
||||||
assert self.client.create_plugin(
|
assert self.client.create_plugin(
|
||||||
'docker-sdk-py/dummy', plugin_data_dir
|
'docker-sdk-py/dummy', plugin_data_dir
|
||||||
|
|
|
@ -36,6 +36,9 @@ class ContainerCollectionTest(BaseIntegrationTest):
|
||||||
with pytest.raises(docker.errors.ImageNotFound):
|
with pytest.raises(docker.errors.ImageNotFound):
|
||||||
client.containers.run("dockerpytest_does_not_exist")
|
client.containers.run("dockerpytest_does_not_exist")
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
docker.constants.IS_WINDOWS_PLATFORM, reason="host mounts on Windows"
|
||||||
|
)
|
||||||
def test_run_with_volume(self):
|
def test_run_with_volume(self):
|
||||||
client = docker.from_env(version=TEST_API_VERSION)
|
client = docker.from_env(version=TEST_API_VERSION)
|
||||||
path = tempfile.mkdtemp()
|
path = tempfile.mkdtemp()
|
||||||
|
|
|
@ -44,7 +44,7 @@ def response(status_code=200, content='', headers=None, reason=None, elapsed=0,
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def fake_resolve_authconfig(authconfig, registry=None):
|
def fake_resolve_authconfig(authconfig, registry=None, *args, **kwargs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ class DockerApiTest(BaseAPIClientTest):
|
||||||
assert result == content
|
assert result == content
|
||||||
|
|
||||||
|
|
||||||
class StreamTest(unittest.TestCase):
|
class UnixSocketStreamTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
socket_dir = tempfile.mkdtemp()
|
socket_dir = tempfile.mkdtemp()
|
||||||
self.build_context = tempfile.mkdtemp()
|
self.build_context = tempfile.mkdtemp()
|
||||||
|
@ -462,7 +462,61 @@ class StreamTest(unittest.TestCase):
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
assert list(stream) == [
|
assert list(stream) == [
|
||||||
str(i).encode() for i in range(50)]
|
str(i).encode() for i in range(50)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TCPSocketStreamTest(unittest.TestCase):
|
||||||
|
text_data = b'''
|
||||||
|
Now, those children out there, they're jumping through the
|
||||||
|
flames in the hope that the god of the fire will make them fruitful.
|
||||||
|
Really, you can't blame them. After all, what girl would not prefer the
|
||||||
|
child of a god to that of some acne-scarred artisan?
|
||||||
|
'''
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.server = six.moves.socketserver.ThreadingTCPServer(
|
||||||
|
('', 0), self.get_handler_class()
|
||||||
|
)
|
||||||
|
self.thread = threading.Thread(target=self.server.serve_forever)
|
||||||
|
self.thread.setDaemon(True)
|
||||||
|
self.thread.start()
|
||||||
|
self.address = 'http://{}:{}'.format(
|
||||||
|
socket.gethostname(), self.server.server_address[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.server.shutdown()
|
||||||
|
self.server.server_close()
|
||||||
|
self.thread.join()
|
||||||
|
|
||||||
|
def get_handler_class(self):
|
||||||
|
text_data = self.text_data
|
||||||
|
|
||||||
|
class Handler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler, object):
|
||||||
|
def do_POST(self):
|
||||||
|
self.send_response(101)
|
||||||
|
self.send_header(
|
||||||
|
'Content-Type', 'application/vnd.docker.raw-stream'
|
||||||
|
)
|
||||||
|
self.send_header('Connection', 'Upgrade')
|
||||||
|
self.send_header('Upgrade', 'tcp')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.flush()
|
||||||
|
time.sleep(0.2)
|
||||||
|
self.wfile.write(text_data)
|
||||||
|
self.wfile.flush()
|
||||||
|
|
||||||
|
return Handler
|
||||||
|
|
||||||
|
def test_read_from_socket(self):
|
||||||
|
with APIClient(base_url=self.address) as client:
|
||||||
|
resp = client._post(client._url('/dummy'), stream=True)
|
||||||
|
data = client._read_from_socket(resp, stream=True, tty=True)
|
||||||
|
results = b''.join(data)
|
||||||
|
|
||||||
|
assert results == self.text_data
|
||||||
|
|
||||||
|
|
||||||
class UserAgentTest(unittest.TestCase):
|
class UserAgentTest(unittest.TestCase):
|
||||||
|
|
Loading…
Reference in New Issue