From 17c6e05e3ab8b34914d7172f6d756be272a7e091 Mon Sep 17 00:00:00 2001 From: Lobsiinvok Date: Mon, 19 Dec 2016 15:07:29 +0800 Subject: [PATCH 1/8] Add filters option to NetworkApiMixin.networks Signed-off-by: Boik --- docker/api/network.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docker/api/network.py b/docker/api/network.py index c58ea6e502..7ccda559d6 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -1,19 +1,23 @@ -import json - from ..errors import InvalidVersion from ..utils import check_resource, minimum_version from ..utils import version_lt +from .. import utils class NetworkApiMixin(object): @minimum_version('1.21') - def networks(self, names=None, ids=None): + def networks(self, names=None, ids=None, filters=None): """ List networks. Similar to the ``docker networks ls`` command. Args: names (list): List of names to filter by ids (list): List of ids to filter by + filters (dict): Filters to be processed on the network list. + Available filters: + - ``driver=[]`` Matches a network's driver. + - ``label=[]`` or ``label=[=]``. + - ``type=["custom"|"builtin"] `` Filters networks by type. Returns: (dict): List of network objects. @@ -23,14 +27,13 @@ def networks(self, names=None, ids=None): If the server returns an error. """ - filters = {} + if filters is None: + filters = {} if names: filters['name'] = names if ids: filters['id'] = ids - - params = {'filters': json.dumps(filters)} - + params = {'filters': utils.convert_filters(filters)} url = self._url("/networks") res = self._get(url, params=params) return self._result(res, json=True) From 1adc9e3abaf46a135fd790739cd5d518fa8742c8 Mon Sep 17 00:00:00 2001 From: Flavio Curella Date: Thu, 8 Dec 2016 12:10:25 -0600 Subject: [PATCH 2/8] Make resources hashable, so that they can be added to `set`s Signed-off-by: Flavio Curella --- docker/models/resource.py | 3 +++ tests/unit/models_resources_test.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/docker/models/resource.py b/docker/models/resource.py index 95712aefc6..ed3900af3a 100644 --- a/docker/models/resource.py +++ b/docker/models/resource.py @@ -23,6 +23,9 @@ def __repr__(self): def __eq__(self, other): return isinstance(other, self.__class__) and self.id == other.id + def __hash__(self): + return hash("%s:%s" % (self.__class__.__name__, self.id)) + @property def id(self): """ diff --git a/tests/unit/models_resources_test.py b/tests/unit/models_resources_test.py index 25c6a3ed0c..5af24ee69f 100644 --- a/tests/unit/models_resources_test.py +++ b/tests/unit/models_resources_test.py @@ -12,3 +12,17 @@ def test_reload(self): container.reload() assert client.api.inspect_container.call_count == 2 assert container.attrs['Name'] == "foobar" + + def test_hash(self): + client = make_fake_client() + container1 = client.containers.get(FAKE_CONTAINER_ID) + my_set = set([container1]) + assert len(my_set) == 1 + + container2 = client.containers.get(FAKE_CONTAINER_ID) + my_set.add(container2) + assert len(my_set) == 1 + + image1 = client.images.get(FAKE_CONTAINER_ID) + my_set.add(image1) + assert len(my_set) == 2 From 8e724fcd5549739b47141e8bafad093cdf8ebe7d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 11 Jan 2017 17:24:50 -0800 Subject: [PATCH 3/8] Use json_stream function in decoded _stream_helper Signed-off-by: Joffrey F --- docker/api/client.py | 49 +++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/docker/api/client.py b/docker/api/client.py index a9fe7d0899..22c32b44d9 100644 --- a/docker/api/client.py +++ b/docker/api/client.py @@ -18,16 +18,20 @@ from .swarm import SwarmApiMixin from .volume import VolumeApiMixin 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) -from ..errors import (DockerException, TLSParameterError, - create_api_error_from_http_exception) +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 +) +from ..errors import ( + DockerException, TLSParameterError, + create_api_error_from_http_exception +) from ..tls import TLSConfig from ..transport import SSLAdapter, UnixAdapter from ..utils import utils, check_resource, update_headers from ..utils.socket import frames_iter +from ..utils.json_stream import json_stream try: from ..transport import NpipeAdapter except ImportError: @@ -274,27 +278,20 @@ def _get_raw_response_socket(self, response): def _stream_helper(self, response, decode=False): """Generator for data coming from a chunked-encoded HTTP response.""" + if response.raw._fp.chunked: - reader = response.raw - while not reader.closed: - # this read call will block until we get a chunk - data = reader.read(1) - if not data: - break - if reader._fp.chunk_left: - data += reader.read(reader._fp.chunk_left) - if decode: - if six.PY3: - data = data.decode('utf-8') - # remove the trailing newline - data = data.strip() - # split the data at any newlines - data_list = data.split("\r\n") - # load and yield each line seperately - for data in data_list: - data = json.loads(data) - yield data - else: + if decode: + for chunk in json_stream(self._stream_helper(response, False)): + yield chunk + else: + reader = response.raw + while not reader.closed: + # this read call will block until we get a chunk + data = reader.read(1) + if not data: + break + if reader._fp.chunk_left: + data += reader.read(reader._fp.chunk_left) yield data else: # Response isn't chunked, meaning we probably From 4699cfd710909cde5c5183a57817bfe12fae6ffd Mon Sep 17 00:00:00 2001 From: "Alejandro E. Brito Monedero" Date: Mon, 16 Jan 2017 08:48:41 +0100 Subject: [PATCH 4/8] Fix #1351 * Fix TypeError when getting the tags property from an image that has no tags. Ex: An image pulled by cryptohash. It is handled like when the image doesn't have defined the RepoTags member. Signed-off-by: Alejandro E. Brito Monedero --- docker/models/images.py | 8 ++++---- tests/unit/models_images_test.py | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docker/models/images.py b/docker/models/images.py index 32068e6927..6f8f4fe273 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -30,10 +30,10 @@ def tags(self): """ The image's tags. """ - return [ - tag for tag in self.attrs.get('RepoTags', []) - if tag != ':' - ] + tags = self.attrs.get('RepoTags') + if tags is None: + tags = [] + return [tag for tag in tags if tag != ':'] def history(self): """ diff --git a/tests/unit/models_images_test.py b/tests/unit/models_images_test.py index 392c58d79f..efb2116660 100644 --- a/tests/unit/models_images_test.py +++ b/tests/unit/models_images_test.py @@ -83,6 +83,11 @@ def test_tags(self): }) assert image.tags == [] + image = Image(attrs={ + 'RepoTags': None + }) + assert image.tags == [] + def test_history(self): client = make_fake_client() image = client.images.get(FAKE_IMAGE_ID) From 9c6d76fdc5cd9eb2c138246d16cb7779da5cae96 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 18 Jan 2017 16:26:22 -0800 Subject: [PATCH 5/8] Prevent issues when installing docker and docker-py in the same environment Signed-off-by: Joffrey F --- setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/setup.py b/setup.py index b82a74f7d1..9fc4ad66e9 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,20 @@ #!/usr/bin/env python +from __future__ import print_function + import codecs import os import sys +import pip + from setuptools import setup, find_packages +if 'docker-py' in [x.project_name for x in pip.get_installed_distributions()]: + print( + 'ERROR: "docker-py" needs to be uninstalled before installing this' + ' package:\npip uninstall docker-py', file=sys.stderr + ) + sys.exit(1) ROOT_DIR = os.path.dirname(__file__) SOURCE_DIR = os.path.join(ROOT_DIR) From 045bad2e461fb17a3399aa970878cf93a283cf9c Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 18 Jan 2017 15:38:53 -0800 Subject: [PATCH 6/8] Fix a number of docs formatting issues Signed-off-by: Joffrey F --- docker/api/container.py | 48 ++++++++++++++++++------------------- docker/api/network.py | 21 ++++++++-------- docker/api/service.py | 32 ++++++++++++------------- docker/api/swarm.py | 4 ++-- docker/models/containers.py | 32 ++++++++++++------------- docker/models/networks.py | 19 ++++++++------- docker/models/services.py | 20 +++++++--------- docker/types/networks.py | 2 +- docker/types/services.py | 14 +++++------ docs/api.rst | 2 -- docs/index.rst | 3 +-- 11 files changed, 96 insertions(+), 101 deletions(-) diff --git a/docker/api/container.py b/docker/api/container.py index afe696ca19..efcae9b0c6 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -388,13 +388,13 @@ def create_container(self, image, command=None, hostname=None, user=None, environment (dict or list): A dictionary or a list of strings in the following format ``["PASSWORD=xxx"]`` or ``{"PASSWORD": "xxx"}``. - dns (list): DNS name servers. Deprecated since API version 1.10. - Use ``host_config`` instead. - dns_opt (list): Additional options to be added to the container's - ``resolv.conf`` file + dns (:py:class:`list`): DNS name servers. Deprecated since API + version 1.10. Use ``host_config`` instead. + dns_opt (:py:class:`list`): Additional options to be added to the + container's ``resolv.conf`` file volumes (str or list): - volumes_from (list): List of container names or Ids to get - volumes from. + volumes_from (:py:class:`list`): List of container names or Ids to + get volumes from. network_disabled (bool): Disable networking name (str): A name for the container entrypoint (str or list): An entrypoint @@ -478,19 +478,19 @@ def create_host_config(self, *args, **kwargs): device_write_bps: Limit write rate (bytes per second) from a device. device_write_iops: Limit write rate (IO per second) from a device. - devices (list): Expose host devices to the container, as a list - of strings in the form + devices (:py:class:`list`): Expose host devices to the container, + as a list of strings in the form ``::``. For example, ``/dev/sda:/dev/xvda:rwm`` allows the container to have read-write access to the host's ``/dev/sda`` via a node named ``/dev/xvda`` inside the container. - dns (list): Set custom DNS servers. - dns_search (list): DNS search domains. + dns (:py:class:`list`): Set custom DNS servers. + dns_search (:py:class:`list`): DNS search domains. extra_hosts (dict): Addtional hostnames to resolve inside the container, as a mapping of hostname to IP address. - group_add (list): List of additional group names and/or IDs that - the container process will run as. + group_add (:py:class:`list`): List of additional group names and/or + IDs that the container process will run as. ipc_mode (str): Set the IPC mode for the container. isolation (str): Isolation technology to use. Default: `None`. links (dict or list of tuples): Either a dictionary mapping name @@ -539,8 +539,8 @@ def create_host_config(self, *args, **kwargs): - ``Name`` One of ``on-failure``, or ``always``. - ``MaximumRetryCount`` Number of times to restart the container on failure. - security_opt (list): A list of string values to customize labels - for MLS systems, such as SELinux. + security_opt (:py:class:`list`): A list of string values to + customize labels for MLS systems, such as SELinux. shm_size (str or int): Size of /dev/shm (e.g. ``1G``). sysctls (dict): Kernel parameters to set in the container. tmpfs (dict): Temporary filesystems to mount, as a dictionary @@ -555,13 +555,13 @@ def create_host_config(self, *args, **kwargs): '/mnt/vol1': 'size=3G,uid=1000' } - ulimits (list): Ulimits to set inside the container, as a list of - dicts. + ulimits (:py:class:`list`): Ulimits to set inside the container, + as a list of dicts. userns_mode (str): Sets the user namespace mode for the container when user namespace remapping option is enabled. Supported values are: ``host`` - volumes_from (list): List of container names or IDs to get - volumes from. + volumes_from (:py:class:`list`): List of container names or IDs to + get volumes from. Returns: @@ -618,17 +618,17 @@ def create_endpoint_config(self, *args, **kwargs): :py:meth:`create_networking_config`. Args: - aliases (list): A list of aliases for this endpoint. Names in - that list can be used within the network to reach the + aliases (:py:class:`list`): A list of aliases for this endpoint. + Names in that list can be used within the network to reach the + container. Defaults to ``None``. + links (:py:class:`list`): A list of links for this endpoint. + Containers declared in this list will be linked to this container. Defaults to ``None``. - links (list): A list of links for this endpoint. Containers - declared in this list will be linked to this container. - Defaults to ``None``. ipv4_address (str): The IP address of this container on the network, using the IPv4 protocol. Defaults to ``None``. ipv6_address (str): The IP address of this container on the network, using the IPv6 protocol. Defaults to ``None``. - link_local_ips (list): A list of link-local (IPv4/IPv6) + link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6) addresses. Returns: diff --git a/docker/api/network.py b/docker/api/network.py index 7ccda559d6..9f6d98fea3 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -11,13 +11,13 @@ def networks(self, names=None, ids=None, filters=None): List networks. Similar to the ``docker networks ls`` command. Args: - names (list): List of names to filter by - ids (list): List of ids to filter by + names (:py:class:`list`): List of names to filter by + ids (:py:class:`list`): List of ids to filter by filters (dict): Filters to be processed on the network list. Available filters: - ``driver=[]`` Matches a network's driver. - ``label=[]`` or ``label=[=]``. - - ``type=["custom"|"builtin"] `` Filters networks by type. + - ``type=["custom"|"builtin"]`` Filters networks by type. Returns: (dict): List of network objects. @@ -169,17 +169,18 @@ def connect_container_to_network(self, container, net_id, Args: container (str): container-id/name to be connected to the network net_id (str): network id - aliases (list): A list of aliases for this endpoint. Names in that - list can be used within the network to reach the container. - Defaults to ``None``. - links (list): A list of links for this endpoint. Containers - declared in this list will be linkedto this container. - Defaults to ``None``. + aliases (:py:class:`list`): A list of aliases for this endpoint. + Names in that list can be used within the network to reach the + container. Defaults to ``None``. + links (:py:class:`list`): A list of links for this endpoint. + Containers declared in this list will be linked to this + container. Defaults to ``None``. ipv4_address (str): The IP address of this container on the network, using the IPv4 protocol. Defaults to ``None``. ipv6_address (str): The IP address of this container on the network, using the IPv6 protocol. Defaults to ``None``. - link_local_ips (list): A list of link-local (IPv4/IPv6) addresses. + link_local_ips (:py:class:`list`): A list of link-local + (IPv4/IPv6) addresses. """ data = { "Container": container, diff --git a/docker/api/service.py b/docker/api/service.py index 7708b75274..0d8421ecb2 100644 --- a/docker/api/service.py +++ b/docker/api/service.py @@ -13,18 +13,18 @@ def create_service( Create a service. Args: - task_template (dict): Specification of the task to start as part - of the new service. + task_template (TaskTemplate): Specification of the task to start as + part of the new service. name (string): User-defined name for the service. Optional. labels (dict): A map of labels to associate with the service. Optional. mode (string): Scheduling mode for the service (``replicated`` or ``global``). Defaults to ``replicated``. - update_config (dict): Specification for the update strategy of the - service. Default: ``None`` - networks (list): List of network names or IDs to attach the - service to. Default: ``None``. - endpoint_config (dict): Properties that can be configured to + update_config (UpdateConfig): Specification for the update strategy + of the service. Default: ``None`` + networks (:py:class:`list`): List of network names or IDs to attach + the service to. Default: ``None``. + endpoint_spec (EndpointSpec): Properties that can be configured to access and load balance a service. Default: ``None``. Returns: @@ -159,7 +159,7 @@ def tasks(self, filters=None): ``label`` and ``desired-state``. Returns: - (list): List of task dictionaries. + (:py:class:`list`): List of task dictionaries. Raises: :py:class:`docker.errors.APIError` @@ -186,20 +186,18 @@ def update_service(self, service, version, task_template=None, name=None, ID). version (int): The version number of the service object being updated. This is required to avoid conflicting writes. - task_template (dict): Specification of the updated task to start - as part of the service. See the [TaskTemplate - class](#TaskTemplate) for details. + task_template (TaskTemplate): Specification of the updated task to + start as part of the service. name (string): New name for the service. Optional. labels (dict): A map of labels to associate with the service. Optional. mode (string): Scheduling mode for the service (``replicated`` or ``global``). Defaults to ``replicated``. - update_config (dict): Specification for the update strategy of the - service. See the [UpdateConfig class](#UpdateConfig) for - details. Default: ``None``. - networks (list): List of network names or IDs to attach the - service to. Default: ``None``. - endpoint_config (dict): Properties that can be configured to + update_config (UpdateConfig): Specification for the update strategy + of the service. Default: ``None``. + networks (:py:class:`list`): List of network names or IDs to attach + the service to. Default: ``None``. + endpoint_spec (EndpointSpec): Properties that can be configured to access and load balance a service. Default: ``None``. Returns: diff --git a/docker/api/swarm.py b/docker/api/swarm.py index 3ada538389..88770562f2 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -143,8 +143,8 @@ def join_swarm(self, remote_addrs, join_token, listen_addr=None, Make this Engine join a swarm that has already been created. Args: - remote_addrs (list): Addresses of one or more manager nodes already - participating in the Swarm to join. + remote_addrs (:py:class:`list`): Addresses of one or more manager + nodes already participating in the Swarm to join. join_token (string): Secret token for joining this Swarm. listen_addr (string): Listen address used for inter-manager communication if the node gets promoted to manager, as well as diff --git a/docker/models/containers.py b/docker/models/containers.py index ad1cb6139c..b1cdd8f870 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -468,17 +468,17 @@ def run(self, image, command=None, stdout=True, stderr=False, device_write_bps: Limit write rate (bytes per second) from a device. device_write_iops: Limit write rate (IO per second) from a device. - devices (list): Expose host devices to the container, as a list - of strings in the form + devices (:py:class:`list`): Expose host devices to the container, + as a list of strings in the form ``::``. For example, ``/dev/sda:/dev/xvda:rwm`` allows the container to have read-write access to the host's ``/dev/sda`` via a node named ``/dev/xvda`` inside the container. - dns (list): Set custom DNS servers. - dns_opt (list): Additional options to be added to the container's - ``resolv.conf`` file. - dns_search (list): DNS search domains. + dns (:py:class:`list`): Set custom DNS servers. + dns_opt (:py:class:`list`): Additional options to be added to the + container's ``resolv.conf`` file. + dns_search (:py:class:`list`): DNS search domains. domainname (str or list): Set custom DNS search domains. entrypoint (str or list): The entrypoint for the container. environment (dict or list): Environment variables to set inside @@ -486,8 +486,8 @@ def run(self, image, command=None, stdout=True, stderr=False, format ``["SOMEVARIABLE=xxx"]``. extra_hosts (dict): Addtional hostnames to resolve inside the container, as a mapping of hostname to IP address. - group_add (list): List of additional group names and/or IDs that - the container process will run as. + group_add (:py:class:`list`): List of additional group names and/or + IDs that the container process will run as. hostname (str): Optional hostname for the container. ipc_mode (str): Set the IPC mode for the container. isolation (str): Isolation technology to use. Default: `None`. @@ -517,8 +517,8 @@ def run(self, image, command=None, stdout=True, stderr=False, behavior. Accepts number between 0 and 100. memswap_limit (str or int): Maximum amount of memory + swap a container is allowed to consume. - networks (list): A list of network names to connect this - container to. + networks (:py:class:`list`): A list of network names to connect + this container to. name (str): The name for this container. network_disabled (bool): Disable networking. network_mode (str): One of: @@ -574,8 +574,8 @@ def run(self, image, command=None, stdout=True, stderr=False, For example: ``{"Name": "on-failure", "MaximumRetryCount": 5}`` - security_opt (list): A list of string values to customize labels - for MLS systems, such as SELinux. + security_opt (:py:class:`list`): A list of string values to + customize labels for MLS systems, such as SELinux. shm_size (str or int): Size of /dev/shm (e.g. ``1G``). stdin_open (bool): Keep ``STDIN`` open even if not attached. stdout (bool): Return logs from ``STDOUT`` when ``detach=False``. @@ -598,8 +598,8 @@ def run(self, image, command=None, stdout=True, stderr=False, } tty (bool): Allocate a pseudo-TTY. - ulimits (list): Ulimits to set inside the container, as a list of - dicts. + ulimits (:py:class:`list`): Ulimits to set inside the container, as + a list of dicts. user (str or int): Username or UID to run commands as inside the container. userns_mode (str): Sets the user namespace mode for the container @@ -621,8 +621,8 @@ def run(self, image, command=None, stdout=True, stderr=False, {'/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'}, '/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'}} - volumes_from (list): List of container names or IDs to get - volumes from. + volumes_from (:py:class:`list`): List of container names or IDs to + get volumes from. working_dir (str): Path to the working directory. Returns: diff --git a/docker/models/networks.py b/docker/models/networks.py index d5e2097295..a80c9f5f8d 100644 --- a/docker/models/networks.py +++ b/docker/models/networks.py @@ -32,17 +32,18 @@ def connect(self, container): container (str): Container to connect to this network, as either an ID, name, or :py:class:`~docker.models.containers.Container` object. - aliases (list): A list of aliases for this endpoint. Names in that - list can be used within the network to reach the container. - Defaults to ``None``. - links (list): A list of links for this endpoint. Containers - declared in this list will be linkedto this container. - Defaults to ``None``. + aliases (:py:class:`list`): A list of aliases for this endpoint. + Names in that list can be used within the network to reach the + container. Defaults to ``None``. + links (:py:class:`list`): A list of links for this endpoint. + Containers declared in this list will be linkedto this + container. Defaults to ``None``. ipv4_address (str): The IP address of this container on the network, using the IPv4 protocol. Defaults to ``None``. ipv6_address (str): The IP address of this container on the network, using the IPv6 protocol. Defaults to ``None``. - link_local_ips (list): A list of link-local (IPv4/IPv6) addresses. + link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6) + addresses. Raises: :py:class:`docker.errors.APIError` @@ -167,8 +168,8 @@ def list(self, *args, **kwargs): List networks. Similar to the ``docker networks ls`` command. Args: - names (list): List of names to filter by. - ids (list): List of ids to filter by. + names (:py:class:`list`): List of names to filter by. + ids (:py:class:`list`): List of ids to filter by. Returns: (list of :py:class:`Network`) The networks on the server. diff --git a/docker/models/services.py b/docker/models/services.py index d70c9e7a08..ef6c3e3a91 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -42,7 +42,7 @@ def tasks(self, filters=None): ``label``, and ``desired-state``. Returns: - (list): List of task dictionaries. + (:py:class:`list`): List of task dictionaries. Raises: :py:class:`docker.errors.APIError` @@ -92,29 +92,27 @@ def create(self, image, command=None, **kwargs): args (list of str): Arguments to the command. constraints (list of str): Placement constraints. container_labels (dict): Labels to apply to the container. - endpoint_spec (dict): Properties that can be configured to + endpoint_spec (EndpointSpec): Properties that can be configured to access and load balance a service. Default: ``None``. env (list of str): Environment variables, in the form ``KEY=val``. labels (dict): Labels to apply to the service. log_driver (str): Log driver to use for containers. log_driver_options (dict): Log driver options. - mode (string): Scheduling mode for the service (``replicated`` or + mode (str): Scheduling mode for the service (``replicated`` or ``global``). Defaults to ``replicated``. mounts (list of str): Mounts for the containers, in the form ``source:target:options``, where options is either ``ro`` or ``rw``. name (str): Name to give to the service. - networks (list): List of network names or IDs to attach the - service to. Default: ``None``. - resources (dict): Resource limits and reservations. For the - format, see the Remote API documentation. - restart_policy (dict): Restart policy for containers. For the - format, see the Remote API documentation. + networks (list of str): List of network names or IDs to attach + the service to. Default: ``None``. + resources (Resources): Resource limits and reservations. + restart_policy (RestartPolicy): Restart policy for containers. stop_grace_period (int): Amount of time to wait for containers to terminate before forcefully killing them. - update_config (dict): Specification for the update strategy of the - service. Default: ``None`` + update_config (UpdateConfig): Specification for the update strategy + of the service. Default: ``None`` user (str): User to run commands as. workdir (str): Working directory for commands to run. diff --git a/docker/types/networks.py b/docker/types/networks.py index 628ea65ad2..1c7b2c9e69 100644 --- a/docker/types/networks.py +++ b/docker/types/networks.py @@ -48,7 +48,7 @@ class IPAMConfig(dict): Args: driver (str): The IPAM driver to use. Defaults to ``default``. - pool_configs (list): A list of pool configurations + pool_configs (:py:class:`list`): A list of pool configurations (:py:class:`~docker.types.IPAMPool`). Defaults to empty list. options (dict): Driver options as a key-value dictionary. Defaults to `None`. diff --git a/docker/types/services.py b/docker/types/services.py index 93503dc054..6e1ad321b9 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -20,7 +20,7 @@ class TaskTemplate(dict): individual container created as part of the service. restart_policy (RestartPolicy): Specification for the restart policy which applies to containers created as part of this service. - placement (list): A list of constraints. + placement (:py:class:`list`): A list of constraints. """ def __init__(self, container_spec, resources=None, restart_policy=None, placement=None, log_driver=None): @@ -62,16 +62,16 @@ class ContainerSpec(dict): image (string): The image name to use for the container. command (string or list): The command to be run in the image. - args (list): Arguments to the command. + args (:py:class:`list`): Arguments to the command. env (dict): Environment variables. dir (string): The working directory for commands to run in. user (string): The user inside the container. labels (dict): A map of labels to associate with the service. - mounts (list): A list of specifications for mounts to be added to - containers created as part of the service. See the - :py:class:`~docker.types.Mount` class for details. + mounts (:py:class:`list`): A list of specifications for mounts to be + added to containers created as part of the service. See the + :py:class:`~docker.types.Mount` class for details. stop_grace_period (int): Amount of time to wait for the container to - terminate before forcefully killing it. + terminate before forcefully killing it. """ def __init__(self, image, command=None, args=None, env=None, workdir=None, user=None, labels=None, mounts=None, stop_grace_period=None): @@ -106,7 +106,7 @@ def __init__(self, image, command=None, args=None, env=None, workdir=None, class Mount(dict): """ Describes a mounted folder's configuration inside a container. A list of - ``Mount``s would be used as part of a + :py:class:`Mount`s would be used as part of a :py:class:`~docker.types.ContainerSpec`. Args: diff --git a/docs/api.rst b/docs/api.rst index 5e59aa7ad1..110b0a7f1d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -19,8 +19,6 @@ Containers :members: :undoc-members: -.. py:module:: docker.api.image - Images ------ diff --git a/docs/index.rst b/docs/index.rst index 7eadf4c7e1..9f484cdbaa 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,13 +73,12 @@ You can manage images: >>> client.images.list() [, , ...] -That's just a taster of what you can do with the Docker SDK for Python. For more, :doc:`take a look at the reference `. +That's just a taste of what you can do with the Docker SDK for Python. For more, :doc:`take a look at the reference `. .. toctree:: :hidden: :maxdepth: 2 - Home client containers images From ac338430b45a272ad076354ec6f5d32aa23c46d2 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 11 Jan 2017 18:07:25 -0800 Subject: [PATCH 7/8] Convert mode argument to valid structure in create_service Signed-off-by: Joffrey F --- docker/api/service.py | 14 ++++++++--- docker/types/__init__.py | 2 +- docker/types/services.py | 35 +++++++++++++++++++++++++++ docs/api.rst | 1 + tests/integration/api_service_test.py | 28 +++++++++++++++++++++ tests/unit/dockertypes_test.py | 33 +++++++++++++++++++++++-- 6 files changed, 106 insertions(+), 7 deletions(-) diff --git a/docker/api/service.py b/docker/api/service.py index 0d8421ecb2..d2621e685c 100644 --- a/docker/api/service.py +++ b/docker/api/service.py @@ -1,5 +1,6 @@ import warnings from .. import auth, errors, utils +from ..types import ServiceMode class ServiceApiMixin(object): @@ -18,8 +19,8 @@ def create_service( name (string): User-defined name for the service. Optional. labels (dict): A map of labels to associate with the service. Optional. - mode (string): Scheduling mode for the service (``replicated`` or - ``global``). Defaults to ``replicated``. + mode (ServiceMode): Scheduling mode for the service (replicated + or global). Defaults to replicated. update_config (UpdateConfig): Specification for the update strategy of the service. Default: ``None`` networks (:py:class:`list`): List of network names or IDs to attach @@ -49,6 +50,9 @@ def create_service( raise errors.DockerException( 'Missing mandatory Image key in ContainerSpec' ) + if mode and not isinstance(mode, dict): + mode = ServiceMode(mode) + registry, repo_name = auth.resolve_repository_name(image) auth_header = auth.get_config_header(self, registry) if auth_header: @@ -191,8 +195,8 @@ def update_service(self, service, version, task_template=None, name=None, name (string): New name for the service. Optional. labels (dict): A map of labels to associate with the service. Optional. - mode (string): Scheduling mode for the service (``replicated`` or - ``global``). Defaults to ``replicated``. + mode (ServiceMode): Scheduling mode for the service (replicated + or global). Defaults to replicated. update_config (UpdateConfig): Specification for the update strategy of the service. Default: ``None``. networks (:py:class:`list`): List of network names or IDs to attach @@ -222,6 +226,8 @@ def update_service(self, service, version, task_template=None, name=None, if labels is not None: data['Labels'] = 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) diff --git a/docker/types/__init__.py b/docker/types/__init__.py index 7230723ee4..8e2fc17472 100644 --- a/docker/types/__init__.py +++ b/docker/types/__init__.py @@ -4,6 +4,6 @@ from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig from .services import ( ContainerSpec, DriverConfig, EndpointSpec, Mount, Resources, RestartPolicy, - TaskTemplate, UpdateConfig + ServiceMode, TaskTemplate, UpdateConfig ) from .swarm import SwarmSpec, SwarmExternalCA diff --git a/docker/types/services.py b/docker/types/services.py index 6e1ad321b9..ec0fcb15f0 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -348,3 +348,38 @@ def convert_service_ports(ports): result.append(port_spec) return result + + +class ServiceMode(dict): + """ + Indicate whether a service should be deployed as a replicated or global + service, and associated parameters + + Args: + mode (string): Can be either ``replicated`` or ``global`` + replicas (int): Number of replicas. For replicated services only. + """ + def __init__(self, mode, replicas=None): + if mode not in ('replicated', 'global'): + raise errors.InvalidArgument( + 'mode must be either "replicated" or "global"' + ) + if mode != 'replicated' and replicas is not None: + raise errors.InvalidArgument( + 'replicas can only be used for replicated mode' + ) + self[mode] = {} + if replicas: + self[mode]['Replicas'] = replicas + + @property + def mode(self): + if 'global' in self: + return 'global' + return 'replicated' + + @property + def replicas(self): + if self.mode != 'replicated': + return None + return self['replicated'].get('Replicas') diff --git a/docs/api.rst b/docs/api.rst index 110b0a7f1d..b5c1e92998 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -110,5 +110,6 @@ Configuration types .. autoclass:: Mount .. autoclass:: Resources .. autoclass:: RestartPolicy +.. autoclass:: ServiceMode .. autoclass:: TaskTemplate .. autoclass:: UpdateConfig diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index fc7940023f..77d7d28f7e 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -251,3 +251,31 @@ def test_create_service_with_env(self): con_spec = svc_info['Spec']['TaskTemplate']['ContainerSpec'] assert 'Env' in con_spec assert con_spec['Env'] == ['DOCKER_PY_TEST=1'] + + def test_create_service_global_mode(self): + container_spec = docker.types.ContainerSpec( + 'busybox', ['echo', 'hello'] + ) + task_tmpl = docker.types.TaskTemplate(container_spec) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, name=name, mode='global' + ) + svc_info = self.client.inspect_service(svc_id) + assert 'Mode' in svc_info['Spec'] + assert 'Global' in svc_info['Spec']['Mode'] + + def test_create_service_replicated_mode(self): + container_spec = docker.types.ContainerSpec( + 'busybox', ['echo', 'hello'] + ) + task_tmpl = docker.types.TaskTemplate(container_spec) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, name=name, + mode=docker.types.ServiceMode('replicated', 5) + ) + svc_info = self.client.inspect_service(svc_id) + assert 'Mode' in svc_info['Spec'] + assert 'Replicated' in svc_info['Spec']['Mode'] + assert svc_info['Spec']['Mode']['Replicated'] == {'Replicas': 5} diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index d11e4f03f0..5c470ffa2f 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -7,7 +7,8 @@ from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import InvalidArgument, InvalidVersion from docker.types import ( - EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, Ulimit, + EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, + ServiceMode, Ulimit, ) try: @@ -260,7 +261,35 @@ def test_create_ipam_config(self): }) -class TestMounts(unittest.TestCase): +class ServiceModeTest(unittest.TestCase): + def test_replicated_simple(self): + mode = ServiceMode('replicated') + assert mode == {'replicated': {}} + assert mode.mode == 'replicated' + assert mode.replicas is None + + def test_global_simple(self): + mode = ServiceMode('global') + assert mode == {'global': {}} + assert mode.mode == 'global' + assert mode.replicas is None + + def test_global_replicas_error(self): + with pytest.raises(InvalidArgument): + ServiceMode('global', 21) + + def test_replicated_replicas(self): + mode = ServiceMode('replicated', 21) + assert mode == {'replicated': {'Replicas': 21}} + assert mode.mode == 'replicated' + assert mode.replicas == 21 + + def test_invalid_mode(self): + with pytest.raises(InvalidArgument): + ServiceMode('foobar') + + +class MountTest(unittest.TestCase): def test_parse_mount_string_ro(self): mount = Mount.parse_mount_string("/foo/bar:/baz:ro") assert mount['Source'] == "/foo/bar" From ab5a189938929a84e52c9ec8f46cbbbf87f485bf Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 19 Jan 2017 17:53:18 -0800 Subject: [PATCH 8/8] Bump 2.0.2 Signed-off-by: Joffrey F --- docker/version.py | 2 +- docs/change-log.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docker/version.py b/docker/version.py index 6979eeca46..b0c244af3e 100644 --- a/docker/version.py +++ b/docker/version.py @@ -1,2 +1,2 @@ -version = "2.0.1" +version = "2.0.2" version_info = tuple([int(d) for d in version.split("-")[0].split(".")]) diff --git a/docs/change-log.md b/docs/change-log.md index 91eafcc4dc..1dda4415d2 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,6 +1,26 @@ Change log ========== +2.0.2 +----- + +[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/28?closed=1) + +### Bugfixes + +* Installation of the package now fails if the `docker-py` package is + installed in order to prevent obscure naming conflicts when both + packages co-exist. +* Added missing `filters` parameter to `APIClient.networks`. +* Resource objects generated by the `DockerClient` are now hashable. +* Fixed a bug where retrieving untagged images using `DockerClient` + would raise a `TypeError` exception. +* `mode` parameter in `create_service` is now properly converted to + a valid data type for the Engine API. Use `ServiceMode` for advanced + configurations. +* Fixed a bug where the decoded `APIClient.events` stream would sometimes raise + an exception when a container is stopped or restarted. + 2.0.1 -----