diff options
author | Jason Pleau <jason@jpleau.ca> | 2018-03-30 15:31:04 -0400 |
---|---|---|
committer | Jason Pleau <jason@jpleau.ca> | 2018-03-30 15:31:04 -0400 |
commit | fa27a6cfe201f8d4241fff59aaa0867cb238122f (patch) | |
tree | 39cb120dc1156f582169450814904ffa716a4f32 /docker/models | |
parent | 7c25f0634f5ff158a492426fa6238e4e6351008b (diff) |
New upstream version 3.2.1
Diffstat (limited to 'docker/models')
-rw-r--r-- | docker/models/__init__.py | 0 | ||||
-rw-r--r-- | docker/models/configs.py | 69 | ||||
-rw-r--r-- | docker/models/containers.py | 1056 | ||||
-rw-r--r-- | docker/models/images.py | 443 | ||||
-rw-r--r-- | docker/models/networks.py | 215 | ||||
-rw-r--r-- | docker/models/nodes.py | 107 | ||||
-rw-r--r-- | docker/models/plugins.py | 200 | ||||
-rw-r--r-- | docker/models/resource.py | 93 | ||||
-rw-r--r-- | docker/models/secrets.py | 69 | ||||
-rw-r--r-- | docker/models/services.py | 352 | ||||
-rw-r--r-- | docker/models/swarm.py | 168 | ||||
-rw-r--r-- | docker/models/volumes.py | 99 |
12 files changed, 2871 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/configs.py b/docker/models/configs.py new file mode 100644 index 0000000..7f23f65 --- /dev/null +++ b/docker/models/configs.py @@ -0,0 +1,69 @@ +from ..api import APIClient +from .resource import Model, Collection + + +class Config(Model): + """A config.""" + id_attribute = 'ID' + + def __repr__(self): + return "<%s: '%s'>" % (self.__class__.__name__, self.name) + + @property + def name(self): + return self.attrs['Spec']['Name'] + + def remove(self): + """ + Remove this config. + + Raises: + :py:class:`docker.errors.APIError` + If config failed to remove. + """ + return self.client.api.remove_config(self.id) + + +class ConfigCollection(Collection): + """Configs on the Docker server.""" + model = Config + + def create(self, **kwargs): + obj = self.client.api.create_config(**kwargs) + return self.prepare_model(obj) + create.__doc__ = APIClient.create_config.__doc__ + + def get(self, config_id): + """ + Get a config. + + Args: + config_id (str): Config ID. + + Returns: + (:py:class:`Config`): The config. + + Raises: + :py:class:`docker.errors.NotFound` + If the config does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_config(config_id)) + + def list(self, **kwargs): + """ + List configs. Similar to the ``docker config ls`` command. + + Args: + filters (dict): Server-side list filtering options. + + Returns: + (list of :py:class:`Config`): The configs. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.configs(**kwargs) + return [self.prepare_model(obj) for obj in resp] diff --git a/docker/models/containers.py b/docker/models/containers.py new file mode 100644 index 0000000..1e06ed6 --- /dev/null +++ b/docker/models/containers.py @@ -0,0 +1,1056 @@ +import copy +import ntpath +from collections import namedtuple + +from ..api import APIClient +from ..constants import DEFAULT_DATA_CHUNK_SIZE +from ..errors import ( + ContainerError, DockerException, ImageNotFound, + create_unexpected_kwargs_error +) +from ..types import HostConfig +from ..utils import version_gte +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 image(self): + """ + The image of the container. + """ + image_id = self.attrs.get('ImageID', self.attrs['Image']) + if image_id is None: + return None + return self.client.images.get(image_id.split(':')[1]) + + @property + def labels(self): + """ + The labels of a container as dictionary. + """ + try: + result = self.attrs['Config'].get('Labels') + return result or {} + except KeyError: + raise DockerException( + 'Label data is not available for sparse objects. Call reload()' + ' to retrieve all information' + ) + + @property + def status(self): + """ + The status of the container. For example, ``running``, or ``exited``. + """ + if isinstance(self.attrs['State'], dict): + return self.attrs['State']['Status'] + return self.attrs['State'] + + 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 + `Engine 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, environment=None, workdir=None): + """ + 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 + stream (bool): Stream response data. Default: False + socket (bool): Return the connection socket to allow custom + read/write operations. Default: False + environment (dict or list): A dictionary or a list of strings in + the following format ``["PASSWORD=xxx"]`` or + ``{"PASSWORD": "xxx"}``. + workdir (str): Path to working directory for this exec session + + Returns: + (ExecResult): A tuple of (exit_code, output) + exit_code: (int): + Exit code for the executed command or ``None`` if + either ``stream```or ``socket`` is ``True``. + output: (generator or str): + If ``stream=True``, a generator yielding response chunks. + If ``socket=True``, a socket object for the connection. + 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, environment=environment, + workdir=workdir + ) + exec_output = self.client.api.exec_start( + resp['Id'], detach=detach, tty=tty, stream=stream, socket=socket + ) + if socket or stream: + return ExecResult(None, exec_output) + + return ExecResult( + self.client.api.exec_inspect(resp['Id'])['ExitCode'], + exec_output + ) + + def export(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE): + """ + Export the contents of the container's filesystem as a tar archive. + + Args: + chunk_size (int): The number of bytes returned by each iteration + of the generator. If ``None``, data will be streamed as it is + received. Default: 2 MB + + 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, chunk_size) + + def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE): + """ + 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 + chunk_size (int): The number of bytes returned by each iteration + of the generator. If ``None``, data will be streamed as it is + received. Default: 2 MB + + 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, chunk_size) + + 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 + until (datetime or int): Show logs that occurred before the given + datetime or integer epoch (in seconds) + + 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 + condition (str): Wait until a container state reaches the given + condition, either ``not-running`` (default), ``next-exit``, + or ``removed`` + + Returns: + (dict): The API's response as a Python dictionary, including + the container's exit code under the ``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. + auto_remove (bool): enable auto-removal of the container on daemon + side when the container's process exits. + 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_count (int): Number of usable CPUs (Windows only). + cpu_percent (int): Usable percentage of the available CPUs + (Windows only). + cpu_period (int): The length of a CPU period in microseconds. + cpu_quota (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``). + cpuset_mems (str): Memory nodes (MEMs) in which to allow execution + (``0-3``, ``0,1``). Only effective on NUMA systems. + detach (bool): Run container in the background and return a + :py:class:`Container` object. + device_cgroup_rules (:py:class:`list`): A list of cgroup rules to + apply to the container. + 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 (:py:class:`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 (: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 + 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 (:py:class:`list`): List of additional group names and/or + IDs that the container process will run as. + healthcheck (dict): Specify a test to perform to check that the + container is healthy. + hostname (str): Optional hostname for the container. + init (bool): Run an init inside the container that forwards + signals and reaps processes + init_path (str): Path to the docker-init binary + 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 (int 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_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. + mounts (:py:class:`list`): Specification for mounts to be added to + the container. More powerful alternative to ``volumes``. Each + item in the list is expected to be a + :py:class:`docker.types.Mount` object. + name (str): The name for this container. + nano_cpus (int): CPU quota in units of 1e-9 CPUs. + network (str): Name of the network this container will be connected + to at creation time. You can connect to additional networks + using :py:meth:`Network.connect`. Incompatible with + ``network_mode``. + 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. + + Incompatible with ``network``. + 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. + platform (str): Platform in the format ``os[/arch[/variant]]``. + Only used if the method needs to pull the requested image. + 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 (: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``. + Default: ``True``. + stderr (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``). + storage_opt (dict): Storage driver options per container as a + key-value mapping. + stream (bool): If true and ``detach`` is false, return a log + generator instead of a string. Ignored if ``detach`` is true. + Default: ``False``. + sysctls (dict): Kernel parameters to set in the container. + tmpfs (dict): Temporary filesystems to mount, as a dictionary + mapping a path inside the container to options for that path. + + For example: + + .. code-block:: python + + { + '/mnt/vol2': '', + '/mnt/vol1': 'size=3G,uid=1000' + } + + tty (bool): Allocate a pseudo-TTY. + 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 + 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 (:py:class:`list`): List of container names or IDs to + get volumes from. + working_dir (str): Path to the working directory. + runtime (str): Runtime to use with this container. + + Returns: + The container logs, either ``STDOUT``, ``STDERR``, or both, + depending on the value of the ``stdout`` and ``stderr`` arguments. + + ``STDOUT`` and ``STDERR`` may be read only if either ``json-file`` + or ``journald`` logging driver used. Thus, if you are using none of + these drivers, a ``None`` object is returned instead. See the + `Engine API documentation + <https://docs.docker.com/engine/api/v1.30/#operation/ContainerLogs/>`_ + for full details. + + 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 + stream = kwargs.pop('stream', False) + detach = kwargs.pop('detach', False) + platform = kwargs.pop('platform', None) + + if detach and remove: + if version_gte(self.client.api._version, '1.25'): + kwargs["auto_remove"] = True + else: + raise RuntimeError("The options 'detach' and 'remove' cannot " + "be used together in api versions < 1.25.") + + if kwargs.get('network') and kwargs.get('network_mode'): + raise RuntimeError( + 'The options "network" and "network_mode" can not be used ' + 'together.' + ) + + try: + container = self.create(image=image, command=command, + detach=detach, **kwargs) + except ImageNotFound: + self.client.images.pull(image, platform=platform) + container = self.create(image=image, command=command, + detach=detach, **kwargs) + + container.start() + + if detach: + return container + + logging_driver = container.attrs['HostConfig']['LogConfig']['Type'] + + out = None + if logging_driver == 'json-file' or logging_driver == 'journald': + out = container.logs( + stdout=stdout, stderr=stderr, stream=True, follow=True + ) + + exit_status = container.wait()['StatusCode'] + if exit_status != 0: + out = None + if not kwargs.get('auto_remove'): + out = container.logs(stdout=False, stderr=True) + + if remove: + container.remove() + if exit_status != 0: + raise ContainerError( + container, exit_status, command, image, out + ) + + return out if stream or out is None else b''.join( + [line for line in 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, + sparse=False): + """ + List containers. Similar to the ``docker ps`` command. + + Args: + all (bool): Show all containers. Only running containers are shown + by default + 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>`_. + + sparse (bool): Do not inspect containers. Returns partial + information, but guaranteed not to block. Use + :py:meth:`Container.reload` on resulting objects to retrieve + all attributes. Default: ``False`` + + 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) + if sparse: + return [self.prepare_model(r) for r in resp] + else: + return [self.get(r['Id']) for r in resp] + + def prune(self, filters=None): + return self.client.api.prune_containers(filters=filters) + prune.__doc__ = APIClient.prune_containers.__doc__ + + +# 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 = [ + 'auto_remove', + 'blkio_weight_device', + 'blkio_weight', + 'cap_add', + 'cap_drop', + 'cgroup_parent', + 'cpu_count', + 'cpu_percent', + 'cpu_period', + 'cpu_quota', + 'cpu_shares', + 'cpuset_cpus', + 'cpuset_mems', + 'cpu_rt_period', + 'cpu_rt_runtime', + 'device_cgroup_rules', + 'device_read_bps', + 'device_read_iops', + 'device_write_bps', + 'device_write_iops', + 'devices', + 'dns_opt', + 'dns_search', + 'dns', + 'extra_hosts', + 'group_add', + 'init', + 'init_path', + 'ipc_mode', + 'isolation', + 'kernel_memory', + 'links', + 'log_config', + 'lxc_conf', + 'mem_limit', + 'mem_reservation', + 'mem_swappiness', + 'memswap_limit', + 'mounts', + 'nano_cpus', + 'network_mode', + 'oom_kill_disable', + 'oom_score_adj', + 'pid_mode', + 'pids_limit', + 'privileged', + 'publish_all_ports', + 'read_only', + 'restart_policy', + 'security_opt', + 'shm_size', + 'storage_opt', + 'sysctls', + 'tmpfs', + 'ulimits', + 'userns_mode', + 'version', + 'volumes_from', + 'runtime' +] + + +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 + + network = kwargs.pop('network', None) + if network: + create_kwargs['networking_config'] = {network: None} + host_config_kwargs['network_mode'] = network + + # 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'] = HostConfig(**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())] + if volumes: + if isinstance(volumes, dict): + create_kwargs['volumes'] = [ + v.get('bind') for v in volumes.values() + ] + else: + create_kwargs['volumes'] = [ + _host_volume_from_bind(v) for v in volumes + ] + return create_kwargs + + +def _host_volume_from_bind(bind): + drive, rest = ntpath.splitdrive(bind) + bits = rest.split(':', 1) + if len(bits) == 1 or bits[1] in ('ro', 'rw'): + return drive + bits[0] + else: + return bits[1].rstrip(':ro').rstrip(':rw') + + +ExecResult = namedtuple('ExecResult', 'exit_code,output') +""" A result of Container.exec_run with the properties ``exit_code`` and + ``output``. """ diff --git a/docker/models/images.py b/docker/models/images.py new file mode 100644 index 0000000..d4893bb --- /dev/null +++ b/docker/models/images.py @@ -0,0 +1,443 @@ +import itertools +import re + +import six + +from ..api import APIClient +from ..constants import DEFAULT_DATA_CHUNK_SIZE +from ..errors import BuildError, ImageLoadError, InvalidArgument +from ..utils import parse_repository_tag +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 labels(self): + """ + The labels of an image as dictionary. + """ + result = self.attrs['Config'].get('Labels') + return result or {} + + @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. + """ + tags = self.attrs.get('RepoTags') + if tags is None: + tags = [] + return [tag for tag in tags 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, chunk_size=DEFAULT_DATA_CHUNK_SIZE): + """ + Get a tarball of an image. Similar to the ``docker save`` command. + + Args: + chunk_size (int): The number of bytes returned by each iteration + of the generator. If ``None``, data will be streamed as it is + received. Default: 2 MB + + Returns: + (generator): A stream of raw archive data. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> image = cli.get_image("busybox:latest") + >>> f = open('/tmp/busybox-latest.tar', 'w') + >>> for chunk in image: + >>> f.write(chunk) + >>> f.close() + """ + return self.client.api.get_image(self.id, chunk_size) + + 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 + """ + return self.client.api.tag(self.id, repository, tag=tag, **kwargs) + + +class RegistryData(Model): + """ + Image metadata stored on the registry, including available platforms. + """ + def __init__(self, image_name, *args, **kwargs): + super(RegistryData, self).__init__(*args, **kwargs) + self.image_name = image_name + + @property + def id(self): + """ + The ID of the object. + """ + return self.attrs['Descriptor']['digest'] + + @property + def short_id(self): + """ + The ID of the image truncated to 10 characters, plus the ``sha256:`` + prefix. + """ + return self.id[:17] + + def pull(self, platform=None): + """ + Pull the image digest. + + Args: + platform (str): The platform to pull the image for. + Default: ``None`` + + Returns: + (:py:class:`Image`): A reference to the pulled image. + """ + repository, _ = parse_repository_tag(self.image_name) + return self.collection.pull(repository, tag=self.id, platform=platform) + + def has_platform(self, platform): + """ + Check whether the given platform identifier is available for this + digest. + + Args: + platform (str or dict): A string using the ``os[/arch[/variant]]`` + format, or a platform dictionary. + + Returns: + (bool): ``True`` if the platform is recognized as available, + ``False`` otherwise. + + Raises: + :py:class:`docker.errors.InvalidArgument` + If the platform argument is not a valid descriptor. + """ + if platform and not isinstance(platform, dict): + parts = platform.split('/') + if len(parts) > 3 or len(parts) < 1: + raise InvalidArgument( + '"{0}" is not a valid platform descriptor'.format(platform) + ) + platform = {'os': parts[0]} + if len(parts) > 2: + platform['variant'] = parts[2] + if len(parts) > 1: + platform['architecture'] = parts[1] + return normalize_platform( + platform, self.client.version() + ) in self.attrs['Platforms'] + + def reload(self): + self.attrs = self.client.api.inspect_distribution(self.image_name) + + reload.__doc__ = Model.reload.__doc__ + + +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 + 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"`` + shmsize (int): Size of `/dev/shm` in bytes. The size must be + greater than 0. If omitted the system uses 64MB + labels (dict): A dictionary of labels to set on the image + cache_from (list): A list of images used for build cache + resolution + target (str): Name of the build-stage to build in a multi-stage + Dockerfile + network_mode (str): networking mode for the run commands during + build + squash (bool): Squash the resulting images layers into a + single layer. + extra_hosts (dict): Extra hosts to add to /etc/hosts in building + containers, as a mapping of hostname to IP address. + platform (str): Platform in the format ``os[/arch[/variant]]``. + isolation (str): Isolation technology used during build. + Default: `None`. + + Returns: + (tuple): The first item is the :py:class:`Image` object for the + image that was build. The second item is a generator of the + build logs as JSON-decoded objects. + + 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) + last_event = None + image_id = None + result_stream, internal_stream = itertools.tee(json_stream(resp)) + for chunk in internal_stream: + if 'error' in chunk: + raise BuildError(chunk['error'], result_stream) + if 'stream' in chunk: + match = re.search( + r'(^Successfully built |sha256:)([0-9a-f]+)$', + chunk['stream'] + ) + if match: + image_id = match.group(2) + last_event = chunk + if image_id: + return (self.get(image_id), result_stream) + raise BuildError(last_event or 'Unknown', result_stream) + + 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 get_registry_data(self, name): + """ + Gets the registry data for an image. + + Args: + name (str): The name of the image. + + Returns: + (:py:class:`RegistryData`): The data object. + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return RegistryData( + image_name=name, + attrs=self.client.api.inspect_distribution(name), + client=self.client, + collection=self, + ) + + 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.get(r["Id"]) 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. + + Returns: + (list of :py:class:`Image`): The images. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.load_image(data) + images = [] + for chunk in resp: + if 'stream' in chunk: + match = re.search( + r'(^Loaded image ID: |^Loaded image: )(.+)$', + chunk['stream'] + ) + if match: + image_id = match.group(2) + images.append(image_id) + if 'error' in chunk: + raise ImageLoadError(chunk['error']) + + return [self.get(i) for i in images] + + def pull(self, repository, tag=None, **kwargs): + """ + Pull an image of the given name and return it. Similar to the + ``docker pull`` command. + If no tag is specified, all tags from that repository will be + pulled. + + 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 + auth_config (dict): Override the credentials that + :py:meth:`~docker.client.DockerClient.login` has set for + this request. ``auth_config`` should contain the ``username`` + and ``password`` keys to be valid. + platform (str): Platform in the format ``os[/arch[/variant]]`` + + Returns: + (:py:class:`Image` or list): The image that has been pulled. + If no ``tag`` was specified, the method will return a list + of :py:class:`Image` objects belonging to this repository. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + + Example: + + >>> # Pull the image tagged `latest` in the busybox repo + >>> image = client.images.pull('busybox:latest') + + >>> # Pull all tags in the busybox repo + >>> images = client.images.pull('busybox') + """ + if not tag: + repository, tag = parse_repository_tag(repository) + + self.client.api.pull(repository, tag=tag, **kwargs) + if tag: + return self.get('{0}{2}{1}'.format( + repository, tag, '@' if tag.startswith('sha256:') else ':' + )) + return self.list(repository) + + 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__ + + def prune(self, filters=None): + return self.client.api.prune_images(filters=filters) + prune.__doc__ = APIClient.prune_images.__doc__ + + +def normalize_platform(platform, engine_info): + if platform is None: + platform = {} + if 'os' not in platform: + platform['os'] = engine_info['Os'] + if 'architecture' not in platform: + platform['architecture'] = engine_info['Arch'] + return platform diff --git a/docker/models/networks.py b/docker/models/networks.py new file mode 100644 index 0000000..1c2fbf2 --- /dev/null +++ b/docker/models/networks.py @@ -0,0 +1,215 @@ +from ..api import APIClient +from ..utils import version_gte +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') or {}).keys() + ] + + def connect(self, container, *args, **kwargs): + """ + 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 (: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 (:py:class:`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, *args, **kwargs + ) + + def disconnect(self, container, *args, **kwargs): + """ + 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, *args, **kwargs + ) + + 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 (IPAMConfig): Optional custom IP scheme for the network. + check_duplicate (bool): Request daemon to check for networks with + same name. Default: ``None``. + 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``. + attachable (bool): If enabled, and the network is in the global + scope, non-service containers on worker nodes will be able to + connect to the network. + scope (str): Specify the network's scope (``local``, ``global`` or + ``swarm``) + ingress (bool): If set, create an ingress network which provides + the routing-mesh in swarm mode. + + 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.types.IPAMPool( + subnet='192.168.52.0/24', + gateway='192.168.52.254' + ) + >>> ipam_config = docker.types.IPAMConfig( + 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, *args, **kwargs): + """ + Get a network by its ID. + + Args: + network_id (str): The ID of the network. + verbose (bool): Retrieve the service details across the cluster in + swarm mode. + scope (str): Filter the network by scope (``swarm``, ``global`` + or ``local``). + + 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, *args, **kwargs) + ) + + def list(self, *args, **kwargs): + """ + List networks. Similar to the ``docker networks ls`` command. + + Args: + 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=[<driver-name>]`` Matches a network's driver. + - ``label=[<key>]`` or ``label=[<key>=<value>]``. + - ``type=["custom"|"builtin"]`` Filters networks by type. + greedy (bool): Fetch more details for each network individually. + You might want this to get the containers attached to them. + + Returns: + (list of :py:class:`Network`) The networks on the server. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + greedy = kwargs.pop('greedy', False) + resp = self.client.api.networks(*args, **kwargs) + networks = [self.prepare_model(item) for item in resp] + if greedy and version_gte(self.client.api._version, '1.28'): + for net in networks: + net.reload() + return networks + + def prune(self, filters=None): + self.client.api.prune_networks(filters=filters) + prune.__doc__ = APIClient.prune_networks.__doc__ diff --git a/docker/models/nodes.py b/docker/models/nodes.py new file mode 100644 index 0000000..8dd9350 --- /dev/null +++ b/docker/models/nodes.py @@ -0,0 +1,107 @@ +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) + + def remove(self, force=False): + """ + Remove this node from the swarm. + + Args: + force (bool): Force remove an active node. Default: `False` + + Returns: + `True` if the request was successful. + + Raises: + :py:class:`docker.errors.NotFound` + If the node doesn't exist in the swarm. + + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.remove_node(self.id, force=force) + + +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/plugins.py b/docker/models/plugins.py new file mode 100644 index 0000000..0688018 --- /dev/null +++ b/docker/models/plugins.py @@ -0,0 +1,200 @@ +from .. import errors +from .resource import Collection, Model + + +class Plugin(Model): + """ + A plugin on the server. + """ + def __repr__(self): + return "<%s: '%s'>" % (self.__class__.__name__, self.name) + + @property + def name(self): + """ + The plugin's name. + """ + return self.attrs.get('Name') + + @property + def enabled(self): + """ + Whether the plugin is enabled. + """ + return self.attrs.get('Enabled') + + @property + def settings(self): + """ + A dictionary representing the plugin's configuration. + """ + return self.attrs.get('Settings') + + def configure(self, options): + """ + Update the plugin's settings. + + Args: + options (dict): A key-value mapping of options. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + self.client.api.configure_plugin(self.name, options) + self.reload() + + def disable(self): + """ + Disable the plugin. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + + self.client.api.disable_plugin(self.name) + self.reload() + + def enable(self, timeout=0): + """ + Enable the plugin. + + Args: + timeout (int): Timeout in seconds. Default: 0 + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + self.client.api.enable_plugin(self.name, timeout) + self.reload() + + def push(self): + """ + Push the plugin to a remote registry. + + Returns: + A dict iterator streaming the status of the upload. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.push_plugin(self.name) + + def remove(self, force=False): + """ + Remove the plugin from the server. + + Args: + force (bool): Remove even if the plugin is enabled. + Default: False + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.client.api.remove_plugin(self.name, force=force) + + def upgrade(self, remote=None): + """ + Upgrade the plugin. + + Args: + remote (string): Remote reference to upgrade to. The + ``:latest`` tag is optional and is the default if omitted. + Default: this plugin's name. + + Returns: + A generator streaming the decoded API logs + """ + if self.enabled: + raise errors.DockerError( + 'Plugin must be disabled before upgrading.' + ) + + if remote is None: + remote = self.name + privileges = self.client.api.plugin_privileges(remote) + for d in self.client.api.upgrade_plugin(self.name, remote, privileges): + yield d + self._reload() + + +class PluginCollection(Collection): + model = Plugin + + def create(self, name, plugin_data_dir, gzip=False): + """ + Create a new plugin. + + Args: + name (string): The name of the plugin. The ``:latest`` tag is + optional, and is the default if omitted. + plugin_data_dir (string): Path to the plugin data directory. + Plugin data directory must contain the ``config.json`` + manifest file and the ``rootfs`` directory. + gzip (bool): Compress the context using gzip. Default: False + + Returns: + (:py:class:`Plugin`): The newly created plugin. + """ + self.client.api.create_plugin(name, plugin_data_dir, gzip) + return self.get(name) + + def get(self, name): + """ + Gets a plugin. + + Args: + name (str): The name of the plugin. + + Returns: + (:py:class:`Plugin`): The plugin. + + Raises: + :py:class:`docker.errors.NotFound` If the plugin does not + exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_plugin(name)) + + def install(self, remote_name, local_name=None): + """ + Pull and install a plugin. + + Args: + remote_name (string): Remote reference for the plugin to + install. The ``:latest`` tag is optional, and is the + default if omitted. + local_name (string): Local name for the pulled plugin. + The ``:latest`` tag is optional, and is the default if + omitted. Optional. + + Returns: + (:py:class:`Plugin`): The installed plugin + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + privileges = self.client.api.plugin_privileges(remote_name) + it = self.client.api.pull_plugin(remote_name, privileges, local_name) + for data in it: + pass + return self.get(local_name or remote_name) + + def list(self): + """ + List plugins installed on the server. + + Returns: + (list of :py:class:`Plugin`): The plugins. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.plugins() + return [self.prepare_model(r) for r in resp] diff --git a/docker/models/resource.py b/docker/models/resource.py new file mode 100644 index 0000000..ed3900a --- /dev/null +++ b/docker/models/resource.py @@ -0,0 +1,93 @@ + +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 + + def __hash__(self): + return hash("%s:%s" % (self.__class__.__name__, self.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 __call__(self, *args, **kwargs): + raise TypeError( + "'{}' object is not callable. You might be trying to use the old " + "(pre-2.0) API - use docker.APIClient if so." + .format(self.__class__.__name__)) + + 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/secrets.py b/docker/models/secrets.py new file mode 100644 index 0000000..ca11ede --- /dev/null +++ b/docker/models/secrets.py @@ -0,0 +1,69 @@ +from ..api import APIClient +from .resource import Model, Collection + + +class Secret(Model): + """A secret.""" + id_attribute = 'ID' + + def __repr__(self): + return "<%s: '%s'>" % (self.__class__.__name__, self.name) + + @property + def name(self): + return self.attrs['Spec']['Name'] + + def remove(self): + """ + Remove this secret. + + Raises: + :py:class:`docker.errors.APIError` + If secret failed to remove. + """ + return self.client.api.remove_secret(self.id) + + +class SecretCollection(Collection): + """Secrets on the Docker server.""" + model = Secret + + def create(self, **kwargs): + obj = self.client.api.create_secret(**kwargs) + return self.prepare_model(obj) + create.__doc__ = APIClient.create_secret.__doc__ + + def get(self, secret_id): + """ + Get a secret. + + Args: + secret_id (str): Secret ID. + + Returns: + (:py:class:`Secret`): The secret. + + Raises: + :py:class:`docker.errors.NotFound` + If the secret does not exist. + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + return self.prepare_model(self.client.api.inspect_secret(secret_id)) + + def list(self, **kwargs): + """ + List secrets. Similar to the ``docker secret ls`` command. + + Args: + filters (dict): Server-side list filtering options. + + Returns: + (list of :py:class:`Secret`): The secrets. + + Raises: + :py:class:`docker.errors.APIError` + If the server returns an error. + """ + resp = self.client.api.secrets(**kwargs) + return [self.prepare_model(obj) for obj in resp] diff --git a/docker/models/services.py b/docker/models/services.py new file mode 100644 index 0000000..125896b --- /dev/null +++ b/docker/models/services.py @@ -0,0 +1,352 @@ +import copy +from docker.errors import create_unexpected_kwargs_error, InvalidArgument +from docker.types import TaskTemplate, ContainerSpec, ServiceMode +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: + (:py:class:`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'] + + if kwargs.get('force_update') is True: + task_template = self.attrs['Spec']['TaskTemplate'] + current_value = int(task_template.get('ForceUpdate', 0)) + kwargs['force_update'] = current_value + 1 + + create_kwargs = _get_create_service_kwargs('update', kwargs) + + return self.client.api.update_service( + self.id, + self.version, + **create_kwargs + ) + + def logs(self, **kwargs): + """ + Get log stream for the service. + Note: This method works only for services with the ``json-file`` + or ``journald`` logging drivers. + + Args: + details (bool): Show extra details provided to logs. + Default: ``False`` + follow (bool): Keep connection open to read logs as they are + sent by the Engine. Default: ``False`` + stdout (bool): Return logs from ``stdout``. Default: ``False`` + stderr (bool): Return logs from ``stderr``. Default: ``False`` + since (int): UNIX timestamp for the logs staring point. + Default: 0 + timestamps (bool): Add timestamps to every log line. + tail (string or int): Number of log lines to be returned, + counting from the current end of the logs. Specify an + integer or ``'all'`` to output all log lines. + Default: ``all`` + + Returns (generator): Logs for the service. + """ + is_tty = self.attrs['Spec']['TaskTemplate']['ContainerSpec'].get( + 'TTY', False + ) + return self.client.api.service_logs(self.id, is_tty=is_tty, **kwargs) + + def scale(self, replicas): + """ + Scale service container. + + Args: + replicas (int): The number of containers that should be running. + + Returns: + ``True``if successful. + """ + + if 'Global' in self.attrs['Spec']['Mode'].keys(): + raise InvalidArgument('Cannot scale a global container') + + service_mode = ServiceMode('replicated', replicas) + return self.client.api.update_service(self.id, self.version, + service_mode, + fetch_current_spec=True) + + def force_update(self): + """ + Force update the service even if no changes require it. + + Returns: + ``True``if successful. + """ + + return self.update(force_update=True, fetch_current_spec=True) + + +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 (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``. + hostname (string): Hostname to set on the container. + isolation (string): Isolation technology used by the service's + containers. Only used for Windows containers. + 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 (ServiceMode): Scheduling mode for the service. + Default:``None`` + 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 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. + secrets (list of :py:class:`docker.types.SecretReference`): List + of secrets accessible to containers for this service. + stop_grace_period (int): Amount of time to wait for + containers to terminate before forcefully killing them. + 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. + tty (boolean): Whether a pseudo-TTY should be allocated. + groups (:py:class:`list`): A list of additional groups that the + container process will run as. + open_stdin (boolean): Open ``stdin`` + read_only (boolean): Mount the container's root filesystem as read + only. + stop_signal (string): Set signal to stop the service's containers + healthcheck (Healthcheck): Healthcheck + configuration for this service. + hosts (:py:class:`dict`): A set of host to IP mappings to add to + the container's `hosts` file. + dns_config (DNSConfig): Specification for DNS + related configurations in resolver configuration file. + configs (:py:class:`list`): List of :py:class:`ConfigReference` + that will be exposed to the service. + privileges (Privileges): Security options for the service's + containers. + + 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, insert_defaults=None): + """ + Get a service. + + Args: + service_id (str): The ID of the service. + insert_defaults (boolean): If true, default values will be merged + into the output. + + 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. + :py:class:`docker.errors.InvalidVersion` + If one of the arguments is not supported with the current + API version. + """ + return self.prepare_model( + self.client.api.inspect_service(service_id, insert_defaults) + ) + + def list(self, **kwargs): + """ + List services. + + Args: + filters (dict): Filters to process on the nodes list. Valid + filters: ``id``, ``name`` , ``label`` and ``mode``. + 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 = [ + 'args', + 'command', + 'configs', + 'dns_config', + 'env', + 'groups', + 'healthcheck', + 'hostname', + 'hosts', + 'image', + 'isolation', + 'labels', + 'mounts', + 'open_stdin', + 'privileges' + 'read_only', + 'secrets', + 'stop_grace_period', + 'stop_signal', + 'tty', + 'user', + 'workdir', +] + +# kwargs to copy straight over to TaskTemplate +TASK_TEMPLATE_KWARGS = [ + 'networks', + 'resources', + 'restart_policy', +] + +# kwargs to copy straight over to create_service +CREATE_SERVICE_KWARGS = [ + 'name', + 'labels', + 'mode', + 'update_config', + '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', {}) + } + + if func_name == 'update': + if 'force_update' in kwargs: + task_template_kwargs['force_update'] = kwargs.pop('force_update') + + # fetch the current spec by default if updating the service + # through the model + fetch_current_spec = kwargs.pop('fetch_current_spec', True) + create_kwargs['fetch_current_spec'] = fetch_current_spec + + # 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..7396e73 --- /dev/null +++ b/docker/models/swarm.py @@ -0,0 +1,168 @@ +from docker.api import APIClient +from docker.errors import APIError +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. + """ + id_attribute = 'ID' + + def __init__(self, *args, **kwargs): + super(Swarm, self).__init__(*args, **kwargs) + if self.client: + try: + self.reload() + except APIError as e: + # FIXME: https://github.com/docker/docker/issues/29192 + if e.response.status_code not in (406, 503): + 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 get_unlock_key(self): + return self.client.api.get_unlock_key() + get_unlock_key.__doc__ = APIClient.get_unlock_key.__doc__ + + def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377', + force_new_cluster=False, **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 + labels (dict): User-defined key/value metadata. + signing_ca_cert (str): The desired signing CA certificate for all + swarm node TLS leaf certificates, in PEM format. + signing_ca_key (str): The desired signing CA key for all swarm + node TLS leaf certificates, in PEM format. + ca_force_rotate (int): An integer whose purpose is to force swarm + to generate a new signing CA certificate and key, if none have + been specified. + autolock_managers (boolean): If set, generate a key and use it to + lock data stored on the managers. + log_driver (DriverConfig): The default log driver to use for tasks + created in the orchestrator. + + 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 = { + 'advertise_addr': advertise_addr, + 'listen_addr': listen_addr, + 'force_new_cluster': force_new_cluster + } + init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**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 unlock(self, key): + return self.client.api.unlock_swarm(key) + unlock.__doc__ = APIClient.unlock_swarm.__doc__ + + 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=self.client.api.create_swarm_spec(**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..3c2e837 --- /dev/null +++ b/docker/models/volumes.py @@ -0,0 +1,99 @@ +from ..api import APIClient +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, force=False): + """ + Remove this volume. + + Args: + force (bool): Force removal of volumes that were already removed + out of band by the volume driver plugin. + Raises: + :py:class:`docker.errors.APIError` + If volume failed to remove. + """ + return self.client.api.remove_volume(self.id, force=force) + + +class VolumeCollection(Collection): + """Volumes on the Docker server.""" + model = Volume + + def create(self, name=None, **kwargs): + """ + Create a volume. + + Args: + name (str): Name of the volume. If not specified, the engine + generates a name. + 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']] + + def prune(self, filters=None): + return self.client.api.prune_volumes(filters=filters) + prune.__doc__ = APIClient.prune_volumes.__doc__ |