diff options
Diffstat (limited to 'docker/models')
-rw-r--r-- | docker/models/__init__.py | 0 | ||||
-rw-r--r-- | docker/models/containers.py | 883 | ||||
-rw-r--r-- | docker/models/images.py | 269 | ||||
-rw-r--r-- | docker/models/networks.py | 181 | ||||
-rw-r--r-- | docker/models/nodes.py | 88 | ||||
-rw-r--r-- | docker/models/resource.py | 84 | ||||
-rw-r--r-- | docker/models/services.py | 240 | ||||
-rw-r--r-- | docker/models/swarm.py | 146 | ||||
-rw-r--r-- | docker/models/volumes.py | 84 |
9 files changed, 1975 insertions, 0 deletions
diff --git a/docker/models/__init__.py b/docker/models/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/docker/models/__init__.py diff --git a/docker/models/containers.py b/docker/models/containers.py new file mode 100644 index 0000000..9682248 --- /dev/null +++ b/docker/models/containers.py @@ -0,0 +1,883 @@ +import copy + +from ..errors import (ContainerError, ImageNotFound, + create_unexpected_kwargs_error) +from ..utils import create_host_config +from .images import Image +from .resource import Collection, Model + + +class Container(Model): + + @property + def name(self): + """ + The name of the container. + """ + if self.attrs.get('Name') is not None: + return self.attrs['Name'].lstrip('/') + + @property + def status(self): + """ + The status of the container. For example, ``running``, or ``exited``. + """ + return self.attrs['State']['Status'] + + def attach(self, **kwargs): + """ + Attach to this container. + + :py:meth:`logs` is a wrapper around this method, which you can + use instead if you want to fetch/stream container output without first + retrieving the entire backlog. + + Args: + stdout (bool): Include stdout. + stderr (bool): Include stderr. + stream (bool): Return container output progressively as an iterator + of strings, rather than a single string. + logs (bool): Include the container's previous output. + + Returns: + By default, the container's output as a single string. + + If ``stream=True``, an iterator of output strings. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.attach(self.id, **kwargs) + + def attach_socket(self, **kwargs): + """ + Like :py:meth:`attach`, but returns the underlying socket-like object + for the HTTP request. + + Args: + params (dict): Dictionary of request parameters (e.g. ``stdout``, + ``stderr``, ``stream``). + ws (bool): Use websockets instead of raw HTTP. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.attach_socket(self.id, **kwargs) + + def commit(self, repository=None, tag=None, **kwargs): + """ + Commit a container to an image. Similar to the ``docker commit`` + command. + + Args: + repository (str): The repository to push the image to + tag (str): The tag to push + message (str): A commit message + author (str): The name of the author + changes (str): Dockerfile instructions to apply while committing + conf (dict): The configuration for the container. See the + `Remote API documentation + <https://docs.docker.com/reference/api/docker_remote_api/>`_ + for full details. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + + resp = self.client.api.commit(self.id, repository=repository, tag=tag, + **kwargs) + return self.client.images.get(resp['Id']) + + def diff(self): + """ + Inspect changes on a container's filesystem. + + Returns: + (str) + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.diff(self.id) + + def exec_run(self, cmd, stdout=True, stderr=True, stdin=False, tty=False, + privileged=False, user='', detach=False, stream=False, + socket=False): + """ + Run a command inside this container. Similar to + ``docker exec``. + + Args: + cmd (str or list): Command to be executed + stdout (bool): Attach to stdout. Default: ``True`` + stderr (bool): Attach to stderr. Default: ``True`` + stdin (bool): Attach to stdin. Default: ``False`` + tty (bool): Allocate a pseudo-TTY. Default: False + privileged (bool): Run as privileged. + user (str): User to execute command as. Default: root + detach (bool): If true, detach from the exec command. + Default: False + tty (bool): Allocate a pseudo-TTY. Default: False + stream (bool): Stream response data. Default: False + + Returns: + (generator or str): If ``stream=True``, a generator yielding + response chunks. A string containing response data otherwise. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.exec_create( + self.id, cmd, stdout=stdout, stderr=stderr, stdin=stdin, tty=tty, + privileged=privileged, user=user + ) + return self.client.api.exec_start( + resp['Id'], detach=detach, tty=tty, stream=stream, socket=socket + ) + + def export(self): + """ + Export the contents of the container's filesystem as a tar archive. + + Returns: + (str): The filesystem tar archive + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.export(self.id) + + def get_archive(self, path): + """ + Retrieve a file or folder from the container in the form of a tar + archive. + + Args: + path (str): Path to the file or folder to retrieve + + Returns: + (tuple): First element is a raw tar data stream. Second element is + a dict containing ``stat`` information on the specified ``path``. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.get_archive(self.id, path) + + def kill(self, signal=None): + """ + Kill or send a signal to the container. + + Args: + signal (str or int): The signal to send. Defaults to ``SIGKILL`` + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + + return self.client.api.kill(self.id, signal=signal) + + def logs(self, **kwargs): + """ + Get logs from this container. Similar to the ``docker logs`` command. + + The ``stream`` parameter makes the ``logs`` function return a blocking + generator you can iterate over to retrieve log output as it happens. + + Args: + stdout (bool): Get ``STDOUT`` + stderr (bool): Get ``STDERR`` + stream (bool): Stream the response + timestamps (bool): Show timestamps + tail (str or int): Output specified number of lines at the end of + logs. Either an integer of number of lines or the string + ``all``. Default ``all`` + since (datetime or int): Show logs since a given datetime or + integer epoch (in seconds) + follow (bool): Follow log output + + Returns: + (generator or str): Logs from the container. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.logs(self.id, **kwargs) + + def pause(self): + """ + Pauses all processes within this container. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.pause(self.id) + + def put_archive(self, path, data): + """ + Insert a file or folder in this container using a tar archive as + source. + + Args: + path (str): Path inside the container where the file(s) will be + extracted. Must exist. + data (bytes): tar data to be extracted + + Returns: + (bool): True if the call succeeds. + + Raises: + :py:class:`~docker.errors.APIError` If an error occurs. + """ + return self.client.api.put_archive(self.id, path, data) + + def remove(self, **kwargs): + """ + Remove this container. Similar to the ``docker rm`` command. + + Args: + v (bool): Remove the volumes associated with the container + link (bool): Remove the specified link and not the underlying + container + force (bool): Force the removal of a running container (uses + ``SIGKILL``) + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.remove_container(self.id, **kwargs) + + def rename(self, name): + """ + Rename this container. Similar to the ``docker rename`` command. + + Args: + name (str): New name for the container + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.rename(self.id, name) + + def resize(self, height, width): + """ + Resize the tty session. + + Args: + height (int): Height of tty session + width (int): Width of tty session + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.resize(self.id, height, width) + + def restart(self, **kwargs): + """ + Restart this container. Similar to the ``docker restart`` command. + + Args: + timeout (int): Number of seconds to try to stop for before killing + the container. Once killed it will then be restarted. Default + is 10 seconds. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.restart(self.id, **kwargs) + + def start(self, **kwargs): + """ + Start this container. Similar to the ``docker start`` command, but + doesn't support attach options. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.start(self.id, **kwargs) + + def stats(self, **kwargs): + """ + Stream statistics for this container. Similar to the + ``docker stats`` command. + + Args: + decode (bool): If set to true, stream will be decoded into dicts + on the fly. False by default. + stream (bool): If set to false, only the current stats will be + returned instead of a stream. True by default. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.stats(self.id, **kwargs) + + def stop(self, **kwargs): + """ + Stops a container. Similar to the ``docker stop`` command. + + Args: + timeout (int): Timeout in seconds to wait for the container to + stop before sending a ``SIGKILL``. Default: 10 + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.stop(self.id, **kwargs) + + def top(self, **kwargs): + """ + Display the running processes of the container. + + Args: + ps_args (str): An optional arguments passed to ps (e.g. ``aux``) + + Returns: + (str): The output of the top + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.top(self.id, **kwargs) + + def unpause(self): + """ + Unpause all processes within the container. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.unpause(self.id) + + def update(self, **kwargs): + """ + Update resource configuration of the containers. + + Args: + blkio_weight (int): Block IO (relative weight), between 10 and 1000 + cpu_period (int): Limit CPU CFS (Completely Fair Scheduler) period + cpu_quota (int): Limit CPU CFS (Completely Fair Scheduler) quota + cpu_shares (int): CPU shares (relative weight) + cpuset_cpus (str): CPUs in which to allow execution + cpuset_mems (str): MEMs in which to allow execution + mem_limit (int or str): Memory limit + mem_reservation (int or str): Memory soft limit + memswap_limit (int or str): Total memory (memory + swap), -1 to + disable swap + kernel_memory (int or str): Kernel memory limit + restart_policy (dict): Restart policy dictionary + + Returns: + (dict): Dictionary containing a ``Warnings`` key. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.update_container(self.id, **kwargs) + + def wait(self, **kwargs): + """ + Block until the container stops, then return its exit code. Similar to + the ``docker wait`` command. + + Args: + timeout (int): Request timeout + + Returns: + (int): The exit code of the container. Returns ``-1`` if the API + responds without a ``StatusCode`` attribute. + + Raises: + :py:class:`requests.exceptions.ReadTimeout` + If the timeout is exceeded. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.wait(self.id, **kwargs) + + +class ContainerCollection(Collection): + model = Container + + def run(self, image, command=None, stdout=True, stderr=False, + remove=False, **kwargs): + """ + Run a container. By default, it will wait for the container to finish + and return its logs, similar to ``docker run``. + + If the ``detach`` argument is ``True``, it will start the container + and immediately return a :py:class:`Container` object, similar to + ``docker run -d``. + + Example: + Run a container and get its output: + + >>> import docker + >>> client = docker.from_env() + >>> client.containers.run('alpine', 'echo hello world') + b'hello world\\n' + + Run a container and detach: + + >>> container = client.containers.run('bfirsh/reticulate-splines', + detach=True) + >>> container.logs() + 'Reticulating spline 1...\\nReticulating spline 2...\\n' + + Args: + image (str): The image to run. + command (str or list): The command to run in the container. + blkio_weight_device: Block IO weight (relative device weight) in + the form of: ``[{"Path": "device_path", "Weight": weight}]``. + blkio_weight: Block IO weight (relative weight), accepts a weight + value between 10 and 1000. + cap_add (list of str): Add kernel capabilities. For example, + ``["SYS_ADMIN", "MKNOD"]``. + cap_drop (list of str): Drop kernel capabilities. + cpu_group (int): The length of a CPU period in microseconds. + cpu_period (int): Microseconds of CPU time that the container can + get in a CPU period. + cpu_shares (int): CPU shares (relative weight). + cpuset_cpus (str): CPUs in which to allow execution (``0-3``, + ``0,1``). + detach (bool): Run container in the background and return a + :py:class:`Container` object. + device_read_bps: Limit read rate (bytes per second) from a device + in the form of: `[{"Path": "device_path", "Rate": rate}]` + device_read_iops: Limit read rate (IO per second) from a device. + 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 + ``<path_on_host>:<path_in_container>:<cgroup_permissions>``. + + 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. + 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 + the container, as a dictionary or a list of strings in the + 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. + 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`. + labels (dict or list): A dictionary of name-value labels (e.g. + ``{"label1": "value1", "label2": "value2"}``) or a list of + names of labels to set with empty values (e.g. + ``["label1", "label2"]``) + links (dict or list of tuples): Either a dictionary mapping name + to alias or as a list of ``(name, alias)`` tuples. + log_config (dict): Logging configuration, as a dictionary with + keys: + + - ``type`` The logging driver name. + - ``config`` A dictionary of configuration for the logging + driver. + + mac_address (str): MAC address to assign to the container. + mem_limit (float or str): Memory limit. Accepts float values + (which represent the memory limit of the created container in + bytes) or a string with a units identification char + (``100000b``, ``1000k``, ``128m``, ``1g``). If a string is + specified without a units character, bytes are assumed as an + intended unit. + mem_limit (str or int): Maximum amount of memory container is + allowed to consume. (e.g. ``1G``). + mem_swappiness (int): Tune a container's memory swappiness + 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. + name (str): The name for this container. + network_disabled (bool): Disable networking. + network_mode (str): One of: + + - ``bridge`` Create a new network stack for the container on + on the bridge network. + - ``none`` No networking for this container. + - ``container:<name|id>`` Reuse another container's network + stack. + - ``host`` Use the host network stack. + 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. + pid_mode (str): If set to ``host``, use the host PID namespace + inside the container. + pids_limit (int): Tune a container's pids limit. Set ``-1`` for + unlimited. + ports (dict): Ports to bind inside the container. + + The keys of the dictionary are the ports to bind inside the + container, either as an integer or a string in the form + ``port/protocol``, where the protocol is either ``tcp`` or + ``udp``. + + The values of the dictionary are the corresponding ports to + open on the host, which can be either: + + - The port number, as an integer. For example, + ``{'2222/tcp': 3333}`` will expose port 2222 inside the + container as port 3333 on the host. + - ``None``, to assign a random host port. For example, + ``{'2222/tcp': None}``. + - A tuple of ``(address, port)`` if you want to specify the + host interface. For example, + ``{'1111/tcp': ('127.0.0.1', 1111)}``. + - A list of integers, if you want to bind multiple host ports + to a single container port. For example, + ``{'1111/tcp': [1234, 4567]}``. + + 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 + only. + remove (bool): Remove the container when it has finished running. + Default: ``False``. + restart_policy (dict): Restart the container when it exits. + Configured as a dictionary with keys: + + - ``Name`` One of ``on-failure``, or ``always``. + - ``MaximumRetryCount`` Number of times to restart the + container on failure. + + For example: + ``{"Name": "on-failure", "MaximumRetryCount": 5}`` + + security_opt (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``. + Default: ``True``. + stdout (bool): Return logs from ``STDERR`` when ``detach=False``. + Default: ``False``. + stop_signal (str): The stop signal to use to stop the container + (e.g. ``SIGINT``). + 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. + + For example: + + .. code-block:: python + + { + '/mnt/vol2': '', + '/mnt/vol1': 'size=3G,uid=1000' + } + + tty (bool): Allocate a pseudo-TTY. + ulimits (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 + when user namespace remapping option is enabled. Supported + values are: ``host`` + volume_driver (str): The name of a volume driver/plugin. + volumes (dict or list): A dictionary to configure volumes mounted + inside the container. The key is either the host path or a + volume name, and the value is a dictionary with the keys: + + - ``bind`` The path to mount the volume inside the container + - ``mode`` Either ``rw`` to mount the volume read/write, or + ``ro`` to mount it read-only. + + For example: + + .. code-block:: python + + {'/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. + working_dir (str): Path to the working directory. + + Returns: + The container logs, either ``STDOUT``, ``STDERR``, or both, + depending on the value of the ``stdout`` and ``stderr`` arguments. + + If ``detach`` is ``True``, a :py:class:`Container` object is + returned instead. + + Raises: + :py:class:`docker.errors.ContainerError` + If the container exits with a non-zero exit code and + ``detach`` is ``False``. + :py:class:`docker.errors.ImageNotFound` + If the specified image does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + if isinstance(image, Image): + image = image.id + detach = kwargs.pop("detach", False) + if detach and remove: + raise RuntimeError("The options 'detach' and 'remove' cannot be " + "used together.") + + try: + container = self.create(image=image, command=command, + detach=detach, **kwargs) + except ImageNotFound: + self.client.images.pull(image) + container = self.create(image=image, command=command, + detach=detach, **kwargs) + + container.start() + + if detach: + return container + + exit_status = container.wait() + if exit_status != 0: + stdout = False + stderr = True + out = container.logs(stdout=stdout, stderr=stderr) + if remove: + container.remove() + if exit_status != 0: + raise ContainerError(container, exit_status, command, image, out) + return out + + def create(self, image, command=None, **kwargs): + """ + Create a container without starting it. Similar to ``docker create``. + + Takes the same arguments as :py:meth:`run`, except for ``stdout``, + ``stderr``, and ``remove``. + + Returns: + A :py:class:`Container` object. + + Raises: + :py:class:`docker.errors.ImageNotFound` + If the specified image does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + if isinstance(image, Image): + image = image.id + kwargs['image'] = image + kwargs['command'] = command + kwargs['version'] = self.client.api._version + create_kwargs = _create_container_args(kwargs) + resp = self.client.api.create_container(**create_kwargs) + return self.get(resp['Id']) + + def get(self, container_id): + """ + Get a container by name or ID. + + Args: + container_id (str): Container name or ID. + + Returns: + A :py:class:`Container` object. + + Raises: + :py:class:`docker.errors.NotFound` + If the container does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.inspect_container(container_id) + return self.prepare_model(resp) + + def list(self, all=False, before=None, filters=None, limit=-1, since=None): + """ + List containers. Similar to the ``docker ps`` command. + + Args: + all (bool): Show all containers. Only running containers are shown + by default trunc (bool): Truncate output + since (str): Show only containers created since Id or Name, include + non-running ones + before (str): Show only container created before Id or Name, + include non-running ones + limit (int): Show `limit` last created containers, include + non-running ones + filters (dict): Filters to be processed on the image list. + Available filters: + + - `exited` (int): Only containers with specified exit code + - `status` (str): One of ``restarting``, ``running``, + ``paused``, ``exited`` + - `label` (str): format either ``"key"`` or ``"key=value"`` + - `id` (str): The id of the container. + - `name` (str): The name of the container. + - `ancestor` (str): Filter by container ancestor. Format of + ``<image-name>[:tag]``, ``<image-id>``, or + ``<image@digest>``. + - `before` (str): Only containers created before a particular + container. Give the container name or id. + - `since` (str): Only containers created after a particular + container. Give container name or id. + + A comprehensive list can be found in the documentation for + `docker ps + <https://docs.docker.com/engine/reference/commandline/ps>`_. + + Returns: + (list of :py:class:`Container`) + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.containers(all=all, before=before, + filters=filters, limit=limit, + since=since) + return [self.get(r['Id']) for r in resp] + + +# kwargs to copy straight from run to create +RUN_CREATE_KWARGS = [ + 'command', + 'detach', + 'domainname', + 'entrypoint', + 'environment', + 'healthcheck', + 'hostname', + 'image', + 'labels', + 'mac_address', + 'name', + 'network_disabled', + 'stdin_open', + 'stop_signal', + 'tty', + 'user', + 'volume_driver', + 'working_dir', +] + +# kwargs to copy straight from run to host_config +RUN_HOST_CONFIG_KWARGS = [ + 'blkio_weight_device', + 'blkio_weight', + 'cap_add', + 'cap_drop', + 'cgroup_parent', + 'cpu_period', + 'cpu_quota', + 'cpu_shares', + 'cpuset_cpus', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'devices', + 'dns_opt', + 'dns_search', + 'dns', + 'extra_hosts', + 'group_add', + 'ipc_mode', + 'isolation', + 'kernel_memory', + 'links', + 'log_config', + 'lxc_conf', + 'mem_limit', + 'mem_reservation', + 'mem_swappiness', + 'memswap_limit', + 'network_mode', + 'oom_kill_disable', + 'oom_score_adj', + 'pid_mode', + 'pids_limit', + 'privileged', + 'publish_all_ports', + 'read_only', + 'restart_policy', + 'security_opt', + 'shm_size', + 'sysctls', + 'tmpfs', + 'ulimits', + 'userns_mode', + 'version', + 'volumes_from', +] + + +def _create_container_args(kwargs): + """ + Convert arguments to create() to arguments to create_container(). + """ + # Copy over kwargs which can be copied directly + create_kwargs = {} + for key in copy.copy(kwargs): + if key in RUN_CREATE_KWARGS: + create_kwargs[key] = kwargs.pop(key) + host_config_kwargs = {} + for key in copy.copy(kwargs): + if key in RUN_HOST_CONFIG_KWARGS: + host_config_kwargs[key] = kwargs.pop(key) + + # Process kwargs which are split over both create and host_config + ports = kwargs.pop('ports', {}) + if ports: + host_config_kwargs['port_bindings'] = ports + + volumes = kwargs.pop('volumes', {}) + if volumes: + host_config_kwargs['binds'] = volumes + + networks = kwargs.pop('networks', []) + if networks: + create_kwargs['networking_config'] = {network: None + for network in networks} + + # All kwargs should have been consumed by this point, so raise + # error if any are left + if kwargs: + raise create_unexpected_kwargs_error('run', kwargs) + + create_kwargs['host_config'] = create_host_config(**host_config_kwargs) + + # Fill in any kwargs which need processing by create_host_config first + port_bindings = create_kwargs['host_config'].get('PortBindings') + if port_bindings: + # sort to make consistent for tests + create_kwargs['ports'] = [tuple(p.split('/', 1)) + for p in sorted(port_bindings.keys())] + binds = create_kwargs['host_config'].get('Binds') + if binds: + create_kwargs['volumes'] = [v.split(':')[0] for v in binds] + return create_kwargs diff --git a/docker/models/images.py b/docker/models/images.py new file mode 100644 index 0000000..e0ff1f4 --- /dev/null +++ b/docker/models/images.py @@ -0,0 +1,269 @@ +import re + +import six + +from ..api import APIClient +from ..errors import BuildError +from ..utils.json_stream import json_stream +from .resource import Collection, Model + + +class Image(Model): + """ + An image on the server. + """ + def __repr__(self): + return "<%s: '%s'>" % (self.__class__.__name__, "', '".join(self.tags)) + + @property + def short_id(self): + """ + The ID of the image truncated to 10 characters, plus the ``sha256:`` + prefix. + """ + if self.id.startswith('sha256:'): + return self.id[:17] + return self.id[:10] + + @property + def tags(self): + """ + The image's tags. + """ + return [ + tag for tag in self.attrs.get('RepoTags', []) + if tag != '<none>:<none>' + ] + + def history(self): + """ + Show the history of an image. + + Returns: + (str): The history of the image. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.history(self.id) + + def save(self): + """ + Get a tarball of an image. Similar to the ``docker save`` command. + + Returns: + (urllib3.response.HTTPResponse object): The response from the + daemon. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> image = cli.get("fedora:latest") + >>> resp = image.save() + >>> f = open('/tmp/fedora-latest.tar', 'w') + >>> f.write(resp.data) + >>> f.close() + """ + return self.client.api.get_image(self.id) + + def tag(self, repository, tag=None, **kwargs): + """ + Tag this image into a repository. Similar to the ``docker tag`` + command. + + Args: + repository (str): The repository to set for the tag + tag (str): The tag name + force (bool): Force + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Returns: + (bool): ``True`` if successful + """ + self.client.api.tag(self.id, repository, tag=tag, **kwargs) + + +class ImageCollection(Collection): + model = Image + + def build(self, **kwargs): + """ + Build an image and return it. Similar to the ``docker build`` + command. Either ``path`` or ``fileobj`` must be set. + + If you have a tar file for the Docker build context (including a + Dockerfile) already, pass a readable file-like object to ``fileobj`` + and also pass ``custom_context=True``. If the stream is compressed + also, set ``encoding`` to the correct value (e.g ``gzip``). + + If you want to get the raw output of the build, use the + :py:meth:`~docker.api.build.BuildApiMixin.build` method in the + low-level API. + + Args: + path (str): Path to the directory containing the Dockerfile + fileobj: A file object to use as the Dockerfile. (Or a file-like + object) + tag (str): A tag to add to the final image + quiet (bool): Whether to return the status + nocache (bool): Don't use the cache when set to ``True`` + rm (bool): Remove intermediate containers. The ``docker build`` + command now defaults to ``--rm=true``, but we have kept the old + default of `False` to preserve backward compatibility + stream (bool): *Deprecated for API version > 1.8 (always True)*. + Return a blocking generator you can iterate over to retrieve + build output as it happens + timeout (int): HTTP timeout + custom_context (bool): Optional if using ``fileobj`` + encoding (str): The encoding for a stream. Set to ``gzip`` for + compressing + pull (bool): Downloads any updates to the FROM image in Dockerfiles + forcerm (bool): Always remove intermediate containers, even after + unsuccessful builds + dockerfile (str): path within the build context to the Dockerfile + buildargs (dict): A dictionary of build arguments + container_limits (dict): A dictionary of limits applied to each + container created by the build process. Valid keys: + + - memory (int): set memory limit for build + - memswap (int): Total memory (memory + swap), -1 to disable + swap + - cpushares (int): CPU shares (relative weight) + - cpusetcpus (str): CPUs in which to allow execution, e.g., + ``"0-3"``, ``"0,1"`` + decode (bool): If set to ``True``, the returned stream will be + decoded into dicts on the fly. Default ``False``. + + Returns: + (:py:class:`Image`): The built image. + + Raises: + :py:class:`docker.errors.BuildError` + If there is an error during the build. + :py:class:`docker.errors.APIError` + If the server returns any other error. + ``TypeError`` + If neither ``path`` nor ``fileobj`` is specified. + """ + resp = self.client.api.build(**kwargs) + if isinstance(resp, six.string_types): + return self.get(resp) + events = list(json_stream(resp)) + if not events: + return BuildError('Unknown') + event = events[-1] + if 'stream' in event: + match = re.search(r'Successfully built ([0-9a-f]+)', + event.get('stream', '')) + if match: + image_id = match.group(1) + return self.get(image_id) + + raise BuildError(event.get('error') or event) + + def get(self, name): + """ + Gets an image. + + Args: + name (str): The name of the image. + + Returns: + (:py:class:`Image`): The image. + + Raises: + :py:class:`docker.errors.ImageNotFound` If the image does not + exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_image(name)) + + def list(self, name=None, all=False, filters=None): + """ + List images on the server. + + Args: + name (str): Only show images belonging to the repository ``name`` + all (bool): Show intermediate image layers. By default, these are + filtered out. + filters (dict): Filters to be processed on the image list. + Available filters: + - ``dangling`` (bool) + - ``label`` (str): format either ``key`` or ``key=value`` + + Returns: + (list of :py:class:`Image`): The images. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.images(name=name, all=all, filters=filters) + return [self.prepare_model(r) for r in resp] + + def load(self, data): + """ + Load an image that was previously saved using + :py:meth:`~docker.models.images.Image.save` (or ``docker save``). + Similar to ``docker load``. + + Args: + data (binary): Image data to be loaded. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.load_image(data) + + def pull(self, name, **kwargs): + """ + Pull an image of the given name and return it. Similar to the + ``docker pull`` command. + + If you want to get the raw pull output, use the + :py:meth:`~docker.api.image.ImageApiMixin.pull` method in the + low-level API. + + Args: + repository (str): The repository to pull + tag (str): The tag to pull + insecure_registry (bool): Use an insecure registry + auth_config (dict): Override the credentials that + :py:meth:`~docker.client.Client.login` has set for + this request. ``auth_config`` should contain the ``username`` + and ``password`` keys to be valid. + + Returns: + (:py:class:`Image`): The image that has been pulled. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> image = client.images.pull('busybox') + """ + self.client.api.pull(name, **kwargs) + return self.get(name) + + def push(self, repository, tag=None, **kwargs): + return self.client.api.push(repository, tag=tag, **kwargs) + push.__doc__ = APIClient.push.__doc__ + + def remove(self, *args, **kwargs): + self.client.api.remove_image(*args, **kwargs) + remove.__doc__ = APIClient.remove_image.__doc__ + + def search(self, *args, **kwargs): + return self.client.api.search(*args, **kwargs) + search.__doc__ = APIClient.search.__doc__ diff --git a/docker/models/networks.py b/docker/models/networks.py new file mode 100644 index 0000000..64af9ad --- /dev/null +++ b/docker/models/networks.py @@ -0,0 +1,181 @@ +from .containers import Container +from .resource import Model, Collection + + +class Network(Model): + """ + A Docker network. + """ + @property + def name(self): + """ + The name of the network. + """ + return self.attrs.get('Name') + + @property + def containers(self): + """ + The containers that are connected to the network, as a list of + :py:class:`~docker.models.containers.Container` objects. + """ + return [ + self.client.containers.get(cid) for cid in + self.attrs.get('Containers', {}).keys() + ] + + def connect(self, container): + """ + Connect a container to this network. + + Args: + 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``. + 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. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + if isinstance(container, Container): + container = container.id + return self.client.api.connect_container_to_network(container, self.id) + + def disconnect(self, container): + """ + Disconnect a container from this network. + + Args: + container (str): Container to disconnect from this network, as + either an ID, name, or + :py:class:`~docker.models.containers.Container` object. + force (bool): Force the container to disconnect from a network. + Default: ``False`` + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + if isinstance(container, Container): + container = container.id + return self.client.api.disconnect_container_from_network(container, + self.id) + + def remove(self): + """ + Remove this network. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.remove_network(self.id) + + +class NetworkCollection(Collection): + """ + Networks on the Docker server. + """ + model = Network + + def create(self, name, *args, **kwargs): + """ + Create a network. Similar to the ``docker network create``. + + Args: + name (str): Name of the network + driver (str): Name of the driver used to create the network + options (dict): Driver options as a key-value dictionary + ipam (dict): Optional custom IP scheme for the network. + Created with :py:meth:`~docker.utils.create_ipam_config`. + check_duplicate (bool): Request daemon to check for networks with + same name. Default: ``True``. + internal (bool): Restrict external access to the network. Default + ``False``. + labels (dict): Map of labels to set on the network. Default + ``None``. + enable_ipv6 (bool): Enable IPv6 on the network. Default ``False``. + + Returns: + (:py:class:`Network`): The network that was created. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + A network using the bridge driver: + + >>> client.networks.create("network1", driver="bridge") + + You can also create more advanced networks with custom IPAM + configurations. For example, setting the subnet to + ``192.168.52.0/24`` and gateway address to ``192.168.52.254``. + + .. code-block:: python + + >>> ipam_pool = docker.utils.create_ipam_pool( + subnet='192.168.52.0/24', + gateway='192.168.52.254' + ) + >>> ipam_config = docker.utils.create_ipam_config( + pool_configs=[ipam_pool] + ) + >>> client.networks.create( + "network1", + driver="bridge", + ipam=ipam_config + ) + + """ + resp = self.client.api.create_network(name, *args, **kwargs) + return self.get(resp['Id']) + + def get(self, network_id): + """ + Get a network by its ID. + + Args: + network_id (str): The ID of the network. + + Returns: + (:py:class:`Network`) The network. + + Raises: + :py:class:`docker.errors.NotFound` + If the network does not exist. + + :py:class:`docker.errors.APIError` + If the server returns an error. + + """ + return self.prepare_model(self.client.api.inspect_network(network_id)) + + 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. + + Returns: + (list of :py:class:`Network`) The networks on the server. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.networks(*args, **kwargs) + return [self.prepare_model(item) for item in resp] diff --git a/docker/models/nodes.py b/docker/models/nodes.py new file mode 100644 index 0000000..0887f99 --- /dev/null +++ b/docker/models/nodes.py @@ -0,0 +1,88 @@ +from .resource import Model, Collection + + +class Node(Model): + """A node in a swarm.""" + id_attribute = 'ID' + + @property + def version(self): + """ + The version number of the service. If this is not the same as the + server, the :py:meth:`update` function will not work and you will + need to call :py:meth:`reload` before calling it again. + """ + return self.attrs.get('Version').get('Index') + + def update(self, node_spec): + """ + Update the node's configuration. + + Args: + node_spec (dict): Configuration settings to update. Any values + not provided will be removed. Default: ``None`` + + Returns: + `True` if the request went through. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> node_spec = {'Availability': 'active', + 'Name': 'node-name', + 'Role': 'manager', + 'Labels': {'foo': 'bar'} + } + >>> node.update(node_spec) + + """ + return self.client.api.update_node(self.id, self.version, node_spec) + + +class NodeCollection(Collection): + """Nodes on the Docker server.""" + model = Node + + def get(self, node_id): + """ + Get a node. + + Args: + node_id (string): ID of the node to be inspected. + + Returns: + A :py:class:`Node` object. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_node(node_id)) + + def list(self, *args, **kwargs): + """ + List swarm nodes. + + Args: + filters (dict): Filters to process on the nodes list. Valid + filters: ``id``, ``name``, ``membership`` and ``role``. + Default: ``None`` + + Returns: + A list of :py:class:`Node` objects. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> client.nodes.list(filters={'role': 'manager'}) + """ + return [ + self.prepare_model(n) + for n in self.client.api.nodes(*args, **kwargs) + ] diff --git a/docker/models/resource.py b/docker/models/resource.py new file mode 100644 index 0000000..9634a24 --- /dev/null +++ b/docker/models/resource.py @@ -0,0 +1,84 @@ + +class Model(object): + """ + A base class for representing a single object on the server. + """ + id_attribute = 'Id' + + def __init__(self, attrs=None, client=None, collection=None): + #: A client pointing at the server that this object is on. + self.client = client + + #: The collection that this model is part of. + self.collection = collection + + #: The raw representation of this object from the API + self.attrs = attrs + if self.attrs is None: + self.attrs = {} + + def __repr__(self): + return "<%s: %s>" % (self.__class__.__name__, self.short_id) + + def __eq__(self, other): + return isinstance(other, self.__class__) and self.id == other.id + + @property + def id(self): + """ + The ID of the object. + """ + return self.attrs.get(self.id_attribute) + + @property + def short_id(self): + """ + The ID of the object, truncated to 10 characters. + """ + return self.id[:10] + + def reload(self): + """ + Load this object from the server again and update ``attrs`` with the + new data. + """ + new_model = self.collection.get(self.id) + self.attrs = new_model.attrs + + +class Collection(object): + """ + A base class for representing all objects of a particular type on the + server. + """ + + #: The type of object this collection represents, set by subclasses + model = None + + def __init__(self, client=None): + #: The client pointing at the server that this collection of objects + #: is on. + self.client = client + + def list(self): + raise NotImplementedError + + def get(self, key): + raise NotImplementedError + + def create(self, attrs=None): + raise NotImplementedError + + def prepare_model(self, attrs): + """ + Create a model from a set of attributes. + """ + if isinstance(attrs, Model): + attrs.client = self.client + attrs.collection = self + return attrs + elif isinstance(attrs, dict): + return self.model(attrs=attrs, client=self.client, collection=self) + else: + raise Exception("Can't create %s from %s" % + (self.model.__name__, attrs)) diff --git a/docker/models/services.py b/docker/models/services.py new file mode 100644 index 0000000..d70c9e7 --- /dev/null +++ b/docker/models/services.py @@ -0,0 +1,240 @@ +import copy +from docker.errors import create_unexpected_kwargs_error +from docker.types import TaskTemplate, ContainerSpec +from .resource import Model, Collection + + +class Service(Model): + """A service.""" + id_attribute = 'ID' + + @property + def name(self): + """The service's name.""" + return self.attrs['Spec']['Name'] + + @property + def version(self): + """ + The version number of the service. If this is not the same as the + server, the :py:meth:`update` function will not work and you will + need to call :py:meth:`reload` before calling it again. + """ + return self.attrs.get('Version').get('Index') + + def remove(self): + """ + Stop and remove the service. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.remove_service(self.id) + + def tasks(self, filters=None): + """ + List the tasks in this service. + + Args: + filters (dict): A map of filters to process on the tasks list. + Valid filters: ``id``, ``name``, ``node``, + ``label``, and ``desired-state``. + + Returns: + (list): List of task dictionaries. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + if filters is None: + filters = {} + filters['service'] = self.id + return self.client.api.tasks(filters=filters) + + def update(self, **kwargs): + """ + Update a service's configuration. Similar to the ``docker service + update`` command. + + Takes the same parameters as :py:meth:`~ServiceCollection.create`. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + # Image is required, so if it hasn't been set, use current image + if 'image' not in kwargs: + spec = self.attrs['Spec']['TaskTemplate']['ContainerSpec'] + kwargs['image'] = spec['Image'] + + create_kwargs = _get_create_service_kwargs('update', kwargs) + + return self.client.api.update_service( + self.id, + self.version, + **create_kwargs + ) + + +class ServiceCollection(Collection): + """Services on the Docker server.""" + model = Service + + def create(self, image, command=None, **kwargs): + """ + Create a service. Similar to the ``docker service create`` command. + + Args: + image (str): The image name to use for the containers. + command (list of str or str): Command to run. + 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 + 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 + ``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. + 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`` + user (str): User to run commands as. + workdir (str): Working directory for commands to run. + + Returns: + (:py:class:`Service`) The created service. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + kwargs['image'] = image + kwargs['command'] = command + create_kwargs = _get_create_service_kwargs('create', kwargs) + service_id = self.client.api.create_service(**create_kwargs) + return self.get(service_id) + + def get(self, service_id): + """ + Get a service. + + Args: + service_id (str): The ID of the service. + + Returns: + (:py:class:`Service`): The service. + + Raises: + :py:class:`docker.errors.NotFound` + If the service does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_service(service_id)) + + def list(self, **kwargs): + """ + List services. + + Args: + filters (dict): Filters to process on the nodes list. Valid + filters: ``id`` and ``name``. Default: ``None``. + + Returns: + (list of :py:class:`Service`): The services. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return [ + self.prepare_model(s) + for s in self.client.api.services(**kwargs) + ] + + +# kwargs to copy straight over to ContainerSpec +CONTAINER_SPEC_KWARGS = [ + 'image', + 'command', + 'args', + 'env', + 'workdir', + 'user', + 'labels', + 'mounts', + 'stop_grace_period', +] + +# kwargs to copy straight over to TaskTemplate +TASK_TEMPLATE_KWARGS = [ + 'resources', + 'restart_policy', +] + +# kwargs to copy straight over to create_service +CREATE_SERVICE_KWARGS = [ + 'name', + 'labels', + 'mode', + 'update_config', + 'networks', + 'endpoint_spec', +] + + +def _get_create_service_kwargs(func_name, kwargs): + # Copy over things which can be copied directly + create_kwargs = {} + for key in copy.copy(kwargs): + if key in CREATE_SERVICE_KWARGS: + create_kwargs[key] = kwargs.pop(key) + container_spec_kwargs = {} + for key in copy.copy(kwargs): + if key in CONTAINER_SPEC_KWARGS: + container_spec_kwargs[key] = kwargs.pop(key) + task_template_kwargs = {} + for key in copy.copy(kwargs): + if key in TASK_TEMPLATE_KWARGS: + task_template_kwargs[key] = kwargs.pop(key) + + if 'container_labels' in kwargs: + container_spec_kwargs['labels'] = kwargs.pop('container_labels') + + if 'constraints' in kwargs: + task_template_kwargs['placement'] = { + 'Constraints': kwargs.pop('constraints') + } + + if 'log_driver' in kwargs: + task_template_kwargs['log_driver'] = { + 'Name': kwargs.pop('log_driver'), + 'Options': kwargs.pop('log_driver_options', {}) + } + + # All kwargs should have been consumed by this point, so raise + # error if any are left + if kwargs: + raise create_unexpected_kwargs_error(func_name, kwargs) + + container_spec = ContainerSpec(**container_spec_kwargs) + task_template_kwargs['container_spec'] = container_spec + create_kwargs['task_template'] = TaskTemplate(**task_template_kwargs) + return create_kwargs diff --git a/docker/models/swarm.py b/docker/models/swarm.py new file mode 100644 index 0000000..38c1e9f --- /dev/null +++ b/docker/models/swarm.py @@ -0,0 +1,146 @@ +from docker.api import APIClient +from docker.errors import APIError +from docker.types import SwarmSpec +from .resource import Model + + +class Swarm(Model): + """ + The server's Swarm state. This a singleton that must be reloaded to get + the current state of the Swarm. + """ + def __init__(self, *args, **kwargs): + super(Swarm, self).__init__(*args, **kwargs) + if self.client: + try: + self.reload() + except APIError as e: + if e.response.status_code != 406: + raise + + @property + def version(self): + """ + The version number of the swarm. If this is not the same as the + server, the :py:meth:`update` function will not work and you will + need to call :py:meth:`reload` before calling it again. + """ + return self.attrs.get('Version').get('Index') + + def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', + force_new_cluster=False, swarm_spec=None, **kwargs): + """ + Initialize a new swarm on this Engine. + + Args: + advertise_addr (str): Externally reachable address advertised to + other nodes. This can either be an address/port combination in + the form ``192.168.1.1:4567``, or an interface followed by a + port number, like ``eth0:4567``. If the port number is omitted, + the port number from the listen address is used. + + If not specified, it will be automatically detected when + possible. + listen_addr (str): Listen address used for inter-manager + communication, as well as determining the networking interface + used for the VXLAN Tunnel Endpoint (VTEP). This can either be + an address/port combination in the form ``192.168.1.1:4567``, + or an interface followed by a port number, like ``eth0:4567``. + If the port number is omitted, the default swarm listening port + is used. Default: ``0.0.0.0:2377`` + force_new_cluster (bool): Force creating a new Swarm, even if + already part of one. Default: False + task_history_retention_limit (int): Maximum number of tasks + history stored. + snapshot_interval (int): Number of logs entries between snapshot. + keep_old_snapshots (int): Number of snapshots to keep beyond the + current snapshot. + log_entries_for_slow_followers (int): Number of log entries to + keep around to sync up slow followers after a snapshot is + created. + heartbeat_tick (int): Amount of ticks (in seconds) between each + heartbeat. + election_tick (int): Amount of ticks (in seconds) needed without a + leader to trigger a new election. + dispatcher_heartbeat_period (int): The delay for an agent to send + a heartbeat to the dispatcher. + node_cert_expiry (int): Automatic expiry for nodes certificates. + external_ca (dict): Configuration for forwarding signing requests + to an external certificate authority. Use + ``docker.types.SwarmExternalCA``. + name (string): Swarm's name + + Returns: + ``True`` if the request went through. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> client.swarm.init( + advertise_addr='eth0', listen_addr='0.0.0.0:5000', + force_new_cluster=False, snapshot_interval=5000, + log_entries_for_slow_followers=1200 + ) + + """ + init_kwargs = {} + for arg in ['advertise_addr', 'listen_addr', 'force_new_cluster']: + if arg in kwargs: + init_kwargs[arg] = kwargs[arg] + del kwargs[arg] + init_kwargs['swarm_spec'] = SwarmSpec(**kwargs) + self.client.api.init_swarm(**init_kwargs) + self.reload() + + def join(self, *args, **kwargs): + return self.client.api.join_swarm(*args, **kwargs) + join.__doc__ = APIClient.join_swarm.__doc__ + + def leave(self, *args, **kwargs): + return self.client.api.leave_swarm(*args, **kwargs) + leave.__doc__ = APIClient.leave_swarm.__doc__ + + def reload(self): + """ + Inspect the swarm on the server and store the response in + :py:attr:`attrs`. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + self.attrs = self.client.api.inspect_swarm() + + def update(self, rotate_worker_token=False, rotate_manager_token=False, + **kwargs): + """ + Update the swarm's configuration. + + It takes the same arguments as :py:meth:`init`, except + ``advertise_addr``, ``listen_addr``, and ``force_new_cluster``. In + addition, it takes these arguments: + + Args: + rotate_worker_token (bool): Rotate the worker join token. Default: + ``False``. + rotate_manager_token (bool): Rotate the manager join token. + Default: ``False``. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + """ + # this seems to have to be set + if kwargs.get('node_cert_expiry') is None: + kwargs['node_cert_expiry'] = 7776000000000000 + + return self.client.api.update_swarm( + version=self.version, + swarm_spec=SwarmSpec(**kwargs), + rotate_worker_token=rotate_worker_token, + rotate_manager_token=rotate_manager_token + ) diff --git a/docker/models/volumes.py b/docker/models/volumes.py new file mode 100644 index 0000000..5a31541 --- /dev/null +++ b/docker/models/volumes.py @@ -0,0 +1,84 @@ +from .resource import Model, Collection + + +class Volume(Model): + """A volume.""" + id_attribute = 'Name' + + @property + def name(self): + """The name of the volume.""" + return self.attrs['Name'] + + def remove(self): + """Remove this volume.""" + return self.client.api.remove_volume(self.id) + + +class VolumeCollection(Collection): + """Volumes on the Docker server.""" + model = Volume + + def create(self, name, **kwargs): + """ + Create a volume. + + Args: + name (str): Name of the volume + driver (str): Name of the driver used to create the volume + driver_opts (dict): Driver options as a key-value dictionary + labels (dict): Labels to set on the volume + + Returns: + (:py:class:`Volume`): The volume created. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> volume = client.volumes.create(name='foobar', driver='local', + driver_opts={'foo': 'bar', 'baz': 'false'}, + labels={"key": "value"}) + + """ + obj = self.client.api.create_volume(name, **kwargs) + return self.prepare_model(obj) + + def get(self, volume_id): + """ + Get a volume. + + Args: + volume_id (str): Volume name. + + Returns: + (:py:class:`Volume`): The volume. + + Raises: + :py:class:`docker.errors.NotFound` + If the volume does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_volume(volume_id)) + + def list(self, **kwargs): + """ + List volumes. Similar to the ``docker volume ls`` command. + + Args: + filters (dict): Server-side list filtering options. + + Returns: + (list of :py:class:`Volume`): The volumes. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.volumes(**kwargs) + if not resp.get('Volumes'): + return [] + return [self.prepare_model(obj) for obj in resp['Volumes']] |