Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d74f1bc
Fix #1575 - Add cpu_rt_period and cpu_rt_runtime args
tz70s Aug 13, 2017
7fa2cb7
Add join_swarm default listen address
Aug 24, 2017
ff86324
Require at least requests v2.14.2 to fix chardet
timvisee Nov 2, 2017
aa3c4f0
Add unlock_swarm and get_unlock_key to APIClient
shin- Nov 7, 2017
3bd053a
Add unlock methods to Swarm model
shin- Nov 7, 2017
c7f1b5f
dev version
shin- Nov 9, 2017
e6cc3c1
Remove test_update_swarm_name
thaJeztah Nov 11, 2017
1d6b5b2
Merge pull request #1805 from thaJeztah/fix-swarm-update-test
shin- Nov 14, 2017
6e5eb2e
Update service using previous spec
rycus86 Nov 14, 2017
b2d08e6
Service model update changes
rycus86 Nov 14, 2017
c78e73b
Attempting to make service update tests less flaky
rycus86 Nov 15, 2017
828b865
Fix resetting ContainerSpec properties to None
rycus86 Nov 15, 2017
7829b72
Fetch network details with network lists greedily
rycus86 Nov 16, 2017
2878900
Fixing integration tests
rycus86 Nov 19, 2017
58c02ca
Merge pull request #1798 from docker/unlock_swarm_support
shin- Nov 21, 2017
f3dbd01
Fix for #1815: make APIClient.stop honor container StopTimeout value
Anvil Nov 20, 2017
6cce101
Add missing call to string format in log message
alexvy86 Nov 21, 2017
36ed843
Only allow greedy queries on the model
rycus86 Nov 21, 2017
94e3d3d
Merge pull request #1819 from alexvy86/alexvy86-patch-1
shin- Nov 21, 2017
5c57050
Fix common issues with build context creation: inaccessible files and…
shin- Nov 30, 2017
8d770b0
Change format of extra hosts
michaelhankin Dec 3, 2017
0134939
Change format in which hosts are being stored for Swarm services
mhank Dec 6, 2017
9d23278
container: fix docstring for containers()
asottile Dec 7, 2017
b4eb3b9
Merge pull request #1831 from asottile/patch-1
shin- Dec 7, 2017
d8f996f
Merge pull request #1825 from docker/fix-context-building
shin- Dec 11, 2017
61bc8be
Add support for order property when updating a service
michaelhankin Dec 12, 2017
a916bfd
Merge pull request #1830 from mhank/1822-reformat-hosts
shin- Dec 14, 2017
aad0c76
Merge pull request #1812 from rycus86/greedy_network_list
shin- Dec 14, 2017
6f77e45
Merge pull request #1816 from Anvil/honor-stoptimeout
shin- Dec 14, 2017
7db7673
Fix URL-quoting for resource names containing spaces
shin- Dec 14, 2017
6c74292
Merge pull request #1789 from timvisee/chardet-fix
shin- Dec 14, 2017
d7bc8ac
Merge branch 'master' of https://github.com/tz70s/docker-py into tz70…
shin- Dec 14, 2017
445cb18
Add integration test for CPU realtime options
shin- Dec 14, 2017
1df979a
Merge branch 'tz70s-master'
shin- Dec 14, 2017
20b5b58
Merge pull request #1835 from docker/1758-url-quote-path
shin- Dec 14, 2017
a66c892
Renaming new argument
rycus86 Dec 14, 2017
49d0958
Correct default value of order parameter
michaelhankin Dec 14, 2017
644a825
Merge pull request #1834 from mhank/1823-support-update-order
shin- Dec 14, 2017
8cfd4cb
Merge pull request #1807 from rycus86/update_service_from_prev_spec
shin- Dec 14, 2017
b6d0dc1
Fixed DEFAULT API VERSION in docstrings.
feliperuhland Dec 15, 2017
6b8dfe4
Retrieve container logs before container exits / is removed
shin- Dec 14, 2017
1fd5958
Merge pull request #1837 from feliperuhland/fix_default_api_version
shin- Dec 15, 2017
bf699c7
Merge pull request #1836 from docker/1813-run-autoremove
shin- Dec 15, 2017
b20f800
fixes create_api_error_from_http_exception()
Dec 2, 2017
5736436
Merge pull request #1828 from pkit/fix_error_from_httpex
shin- Dec 18, 2017
ac68a36
Merge pull request #1727 from mbelang/join-swarn-default-listen-address
shin- Dec 19, 2017
f10c008
Bump 2.7.0 + changelog
shin- Dec 19, 2017
598f167
Don't attempt to retrieve container's stderr if `auto_remove` was set
shin- Dec 19, 2017
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
18 changes: 9 additions & 9 deletions docker/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,21 @@ class APIClient(
>>> import docker
>>> client = docker.APIClient(base_url='unix://var/run/docker.sock')
>>> client.version()
{u'ApiVersion': u'1.24',
{u'ApiVersion': u'1.33',
u'Arch': u'amd64',
u'BuildTime': u'2016-09-27T23:38:15.810178467+00:00',
u'Experimental': True,
u'GitCommit': u'45bed2c',
u'GoVersion': u'go1.6.3',
u'KernelVersion': u'4.4.22-moby',
u'BuildTime': u'2017-11-19T18:46:37.000000000+00:00',
u'GitCommit': u'f4ffd2511c',
u'GoVersion': u'go1.9.2',
u'KernelVersion': u'4.14.3-1-ARCH',
u'MinAPIVersion': u'1.12',
u'Os': u'linux',
u'Version': u'1.12.2-rc1'}
u'Version': u'17.10.0-ce'}

Args:
base_url (str): URL to the Docker server. For example,
``unix:///var/run/docker.sock`` or ``tcp://127.0.0.1:1234``.
version (str): The version of the API to use. Set to ``auto`` to
automatically detect the server's version. Default: ``1.26``
automatically detect the server's version. Default: ``1.30``
timeout (int): Default timeout for API calls, in seconds.
tls (bool or :py:class:`~docker.tls.TLSConfig`): Enable TLS. Pass
``True`` to enable it with default options, or pass a
Expand Down Expand Up @@ -206,7 +206,7 @@ def _url(self, pathfmt, *args, **kwargs):
'instead'.format(arg, type(arg))
)

quote_f = partial(six.moves.urllib.parse.quote_plus, safe="/:")
quote_f = partial(six.moves.urllib.parse.quote, safe="/:")
args = map(quote_f, args)

if kwargs.get('versioned_api', True):
Expand Down
15 changes: 11 additions & 4 deletions docker/api/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ def containers(self, quiet=False, all=False, trunc=False, latest=False,
Args:
quiet (bool): Only display numeric Ids
all (bool): Show all containers. Only running containers are shown
by default trunc (bool): Truncate output
by default
trunc (bool): Truncate output
latest (bool): Show only the latest created container, include
non-running ones.
since (str): Show only containers created since Id or Name, include
Expand Down Expand Up @@ -1112,20 +1113,26 @@ def stats(self, container, decode=None, stream=True):
json=True)

@utils.check_resource('container')
def stop(self, container, timeout=10):
def stop(self, container, timeout=None):
"""
Stops a container. Similar to the ``docker stop`` command.

Args:
container (str): The container to stop
timeout (int): Timeout in seconds to wait for the container to
stop before sending a ``SIGKILL``. Default: 10
stop before sending a ``SIGKILL``. If None, then the
StopTimeout value of the container will be used.
Default: None

Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
params = {'t': timeout}
if timeout is None:
params = {}
timeout = 10
else:
params = {'t': timeout}
url = self._url("/containers/{0}/stop", container)

res = self._post(url, params=params,
Expand Down
81 changes: 67 additions & 14 deletions docker/api/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def raise_version_error(param, min_version):
if 'Monitor' in update_config:
raise_version_error('UpdateConfig.monitor', '1.25')

if utils.version_lt(version, '1.29'):
if 'Order' in update_config:
raise_version_error('UpdateConfig.order', '1.29')

if task_template is not None:
if 'ForceUpdate' in task_template and utils.version_lt(
version, '1.25'):
Expand Down Expand Up @@ -62,6 +66,21 @@ def raise_version_error(param, min_version):
raise_version_error('ContainerSpec.privileges', '1.30')


def _merge_task_template(current, override):
merged = current.copy()
if override is not None:
for ts_key, ts_value in override.items():
if ts_key == 'ContainerSpec':
if 'ContainerSpec' not in merged:
merged['ContainerSpec'] = {}
for cs_key, cs_value in override['ContainerSpec'].items():
if cs_value is not None:
merged['ContainerSpec'][cs_key] = cs_value
elif ts_value is not None:
merged[ts_key] = ts_value
return merged


class ServiceApiMixin(object):
@utils.minimum_version('1.24')
def create_service(
Expand Down Expand Up @@ -306,7 +325,7 @@ def tasks(self, filters=None):
def update_service(self, service, version, task_template=None, name=None,
labels=None, mode=None, update_config=None,
networks=None, endpoint_config=None,
endpoint_spec=None):
endpoint_spec=None, fetch_current_spec=False):
"""
Update a service.

Expand All @@ -328,6 +347,8 @@ def update_service(self, service, version, task_template=None, name=None,
the service to. Default: ``None``.
endpoint_spec (EndpointSpec): Properties that can be configured to
access and load balance a service. Default: ``None``.
fetch_current_spec (boolean): Use the undefined settings from the
current specification of the service. Default: ``False``

Returns:
``True`` if successful.
Expand All @@ -345,32 +366,64 @@ def update_service(self, service, version, task_template=None, name=None,

_check_api_features(self._version, task_template, update_config)

if fetch_current_spec:
inspect_defaults = True
if utils.version_lt(self._version, '1.29'):
inspect_defaults = None
current = self.inspect_service(
service, insert_defaults=inspect_defaults
)['Spec']

else:
current = {}

url = self._url('/services/{0}/update', service)
data = {}
headers = {}
if name is not None:
data['Name'] = name
if labels is not None:
data['Labels'] = labels

data['Name'] = current.get('Name') if name is None else name

data['Labels'] = current.get('Labels') if labels is None else labels

if mode is not None:
if not isinstance(mode, dict):
mode = ServiceMode(mode)
data['Mode'] = mode
if task_template is not None:
image = task_template.get('ContainerSpec', {}).get('Image', None)
if image is not None:
registry, repo_name = auth.resolve_repository_name(image)
auth_header = auth.get_config_header(self, registry)
if auth_header:
headers['X-Registry-Auth'] = auth_header
data['TaskTemplate'] = task_template
else:
data['Mode'] = current.get('Mode')

data['TaskTemplate'] = _merge_task_template(
current.get('TaskTemplate', {}), task_template
)

container_spec = data['TaskTemplate'].get('ContainerSpec', {})
image = container_spec.get('Image', None)
if image is not None:
registry, repo_name = auth.resolve_repository_name(image)
auth_header = auth.get_config_header(self, registry)
if auth_header:
headers['X-Registry-Auth'] = auth_header

if update_config is not None:
data['UpdateConfig'] = update_config
else:
data['UpdateConfig'] = current.get('UpdateConfig')

if networks is not None:
data['Networks'] = utils.convert_service_networks(networks)
converted_networks = utils.convert_service_networks(networks)
data['TaskTemplate']['Networks'] = converted_networks
elif data['TaskTemplate'].get('Networks') is None:
current_task_template = current.get('TaskTemplate', {})
current_networks = current_task_template.get('Networks')
if current_networks is None:
current_networks = current.get('Networks')
if current_networks is not None:
data['TaskTemplate']['Networks'] = current_networks

if endpoint_spec is not None:
data['EndpointSpec'] = endpoint_spec
else:
data['EndpointSpec'] = current.get('EndpointSpec')

resp = self._post_json(
url, data=data, params={'version': version}, headers=headers
Expand Down
52 changes: 50 additions & 2 deletions docker/api/swarm.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
from six.moves import http_client
from .. import errors
from .. import types
from .. import utils

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -68,6 +70,16 @@ def create_swarm_spec(self, *args, **kwargs):
kwargs['external_cas'] = [ext_ca]
return types.SwarmSpec(self._version, *args, **kwargs)

@utils.minimum_version('1.24')
def get_unlock_key(self):
"""
Get the unlock key for this Swarm manager.

Returns:
A ``dict`` containing an ``UnlockKey`` member
"""
return self._result(self._get(self._url('/swarm/unlockkey')), True)

@utils.minimum_version('1.24')
def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
force_new_cluster=False, swarm_spec=None):
Expand Down Expand Up @@ -152,7 +164,7 @@ def inspect_node(self, node_id):
return self._result(self._get(url), True)

@utils.minimum_version('1.24')
def join_swarm(self, remote_addrs, join_token, listen_addr=None,
def join_swarm(self, remote_addrs, join_token, listen_addr='0.0.0.0:2377',
advertise_addr=None):
"""
Make this Engine join a swarm that has already been created.
Expand Down Expand Up @@ -270,10 +282,46 @@ def remove_node(self, node_id, force=False):
self._raise_for_status(res)
return True

@utils.minimum_version('1.24')
def unlock_swarm(self, key):
"""
Unlock a locked swarm.

Args:
key (string): The unlock key as provided by
:py:meth:`get_unlock_key`

Raises:
:py:class:`docker.errors.InvalidArgument`
If the key argument is in an incompatible format

:py:class:`docker.errors.APIError`
If the server returns an error.

Returns:
`True` if the request was successful.

Example:

>>> key = client.get_unlock_key()
>>> client.unlock_node(key)

"""
if isinstance(key, dict):
if 'UnlockKey' not in key:
raise errors.InvalidArgument('Invalid unlock key format')
else:
key = {'UnlockKey': key}

url = self._url('/swarm/unlock')
res = self._post_json(url, data=key)
self._raise_for_status(res)
return True

@utils.minimum_version('1.24')
def update_node(self, node_id, version, node_spec=None):
"""
Update the Node's configuration
Update the node's configuration

Args:

Expand Down
2 changes: 1 addition & 1 deletion docker/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def parse_auth(entries, raise_on_error=False):
# https://github.com/docker/compose/issues/3265
log.debug(
'Auth data for {0} is absent. Client might be using a '
'credentials store instead.'
'credentials store instead.'.format(registry)
)
conf[registry] = {}
continue
Expand Down
4 changes: 2 additions & 2 deletions docker/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DockerClient(object):
base_url (str): URL to the Docker server. For example,
``unix:///var/run/docker.sock`` or ``tcp://127.0.0.1:1234``.
version (str): The version of the API to use. Set to ``auto`` to
automatically detect the server's version. Default: ``1.26``
automatically detect the server's version. Default: ``1.30``
timeout (int): Default timeout for API calls, in seconds.
tls (bool or :py:class:`~docker.tls.TLSConfig`): Enable TLS. Pass
``True`` to enable it with default options, or pass a
Expand Down Expand Up @@ -60,7 +60,7 @@ def from_env(cls, **kwargs):

Args:
version (str): The version of the API to use. Set to ``auto`` to
automatically detect the server's version. Default: ``1.26``
automatically detect the server's version. Default: ``1.30``
timeout (int): Default timeout for API calls, in seconds.
ssl_version (int): A valid `SSL version`_.
assert_hostname (bool): Verify the hostname of the server.
Expand Down
2 changes: 1 addition & 1 deletion docker/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def create_api_error_from_http_exception(e):
try:
explanation = response.json()['message']
except ValueError:
explanation = response.content.strip()
explanation = (response.content or '').strip()
cls = APIError
if response.status_code == 404:
if explanation and ('No such image' in str(explanation) or
Expand Down
31 changes: 22 additions & 9 deletions docker/models/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,9 @@ def run(self, image, command=None, stdout=True, stderr=False,
(e.g. ``SIGINT``).
storage_opt (dict): Storage driver options per container as a
key-value mapping.
stream (bool): If true and ``detach`` is false, return a log
generator instead of a string. Ignored if ``detach`` is true.
Default: ``False``.
sysctls (dict): Kernel parameters to set in the container.
tmpfs (dict): Temporary filesystems to mount, as a dictionary
mapping a path inside the container to options for that path.
Expand Down Expand Up @@ -696,6 +699,7 @@ def run(self, image, command=None, stdout=True, stderr=False,
"""
if isinstance(image, Image):
image = image.id
stream = kwargs.pop('stream', False)
detach = kwargs.pop("detach", False)
if detach and remove:
if version_gte(self.client.api._version, '1.25'):
Expand Down Expand Up @@ -723,23 +727,30 @@ def run(self, image, command=None, stdout=True, stderr=False,
if detach:
return container

exit_status = container.wait()
if exit_status != 0:
stdout = False
stderr = True

logging_driver = container.attrs['HostConfig']['LogConfig']['Type']

out = None
if logging_driver == 'json-file' or logging_driver == 'journald':
out = container.logs(stdout=stdout, stderr=stderr)
else:
out = container.logs(
stdout=stdout, stderr=stderr, stream=True, follow=True
)

exit_status = container.wait()
if exit_status != 0:
out = None
if not kwargs.get('auto_remove'):
out = container.logs(stdout=False, stderr=True)

if remove:
container.remove()
if exit_status != 0:
raise ContainerError(container, exit_status, command, image, out)
return out
raise ContainerError(
container, exit_status, command, image, out
)

return out if stream or out is None else b''.join(
[line for line in out]
)

def create(self, image, command=None, **kwargs):
"""
Expand Down Expand Up @@ -873,6 +884,8 @@ def prune(self, filters=None):
'cpu_shares',
'cpuset_cpus',
'cpuset_mems',
'cpu_rt_period',
'cpu_rt_runtime',
'device_read_bps',
'device_read_iops',
'device_write_bps',
Expand Down
Loading