This commit is contained in:
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,70 @@
|
||||
from ..api import APIClient
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
class Config(Model):
|
||||
"""A config."""
|
||||
id_attribute = 'ID'
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{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)
|
||||
obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
|
||||
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]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,505 @@
|
||||
import itertools
|
||||
import re
|
||||
import warnings
|
||||
|
||||
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):
|
||||
tag_str = "', '".join(self.tags)
|
||||
return f"<{self.__class__.__name__}: '{tag_str}'>"
|
||||
|
||||
@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 12 characters, plus the ``sha256:``
|
||||
prefix.
|
||||
"""
|
||||
if self.id.startswith('sha256:'):
|
||||
return self.id[:19]
|
||||
return self.id[:12]
|
||||
|
||||
@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:
|
||||
(list): 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 remove(self, force=False, noprune=False):
|
||||
"""
|
||||
Remove this image.
|
||||
|
||||
Args:
|
||||
force (bool): Force removal of the image
|
||||
noprune (bool): Do not delete untagged parents
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
return self.client.api.remove_image(
|
||||
self.id,
|
||||
force=force,
|
||||
noprune=noprune,
|
||||
)
|
||||
|
||||
def save(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE, named=False):
|
||||
"""
|
||||
Get a tarball of an image. Similar to the ``docker save`` command.
|
||||
|
||||
Args:
|
||||
chunk_size (int): The generator will return up to that much data
|
||||
per iteration, but may return less. If ``None``, data will be
|
||||
streamed as it is received. Default: 2 MB
|
||||
named (str or bool): If ``False`` (default), the tarball will not
|
||||
retain repository and tag information for this image. If set
|
||||
to ``True``, the first tag in the :py:attr:`~tags` list will
|
||||
be used to identify the image. Alternatively, any element of
|
||||
the :py:attr:`~tags` list can be used as an argument to use
|
||||
that specific tag as the saved identifier.
|
||||
|
||||
Returns:
|
||||
(generator): A stream of raw archive data.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
|
||||
Example:
|
||||
|
||||
>>> image = cli.images.get("busybox:latest")
|
||||
>>> f = open('/tmp/busybox-latest.tar', 'wb')
|
||||
>>> for chunk in image.save():
|
||||
>>> f.write(chunk)
|
||||
>>> f.close()
|
||||
"""
|
||||
img = self.id
|
||||
if named:
|
||||
img = self.tags[0] if self.tags else img
|
||||
if isinstance(named, str):
|
||||
if named not in self.tags:
|
||||
raise InvalidArgument(
|
||||
f"{named} is not a valid tag for this image"
|
||||
)
|
||||
img = named
|
||||
|
||||
return self.client.api.get_image(img, 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().__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 12 characters, plus the ``sha256:``
|
||||
prefix.
|
||||
"""
|
||||
return self.id[:19]
|
||||
|
||||
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(
|
||||
f'"{platform}" is not a valid platform descriptor'
|
||||
)
|
||||
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 already have a tar file for the Docker build context (including
|
||||
a Dockerfile), pass a readable file-like object to ``fileobj``
|
||||
and also pass ``custom_context=True``. If the stream is also
|
||||
compressed, 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`.
|
||||
use_config_proxy (bool): If ``True``, and if the docker client
|
||||
configuration file (``~/.docker/config.json`` by default)
|
||||
contains a proxy configuration, the corresponding environment
|
||||
variables will be set in the container being built.
|
||||
|
||||
Returns:
|
||||
(tuple): The first item is the :py:class:`Image` object for the
|
||||
image that was built. 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, str):
|
||||
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, auth_config=None):
|
||||
"""
|
||||
Gets the registry data for an image.
|
||||
|
||||
Args:
|
||||
name (str): The name of the image.
|
||||
auth_config (dict): Override the credentials that are found in the
|
||||
config for this request. ``auth_config`` should contain the
|
||||
``username`` and ``password`` keys to be valid.
|
||||
|
||||
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, auth_config),
|
||||
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|list): format either ``"key"``, ``"key=value"``
|
||||
or a list of such.
|
||||
|
||||
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, all_tags=False, **kwargs):
|
||||
"""
|
||||
Pull an image of the given name and return it. Similar to the
|
||||
``docker pull`` command.
|
||||
If ``tag`` is ``None`` or empty, it is set to ``latest``.
|
||||
If ``all_tags`` is set, the ``tag`` parameter is ignored and all image
|
||||
tags 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 are found in the
|
||||
config for this request. ``auth_config`` should contain the
|
||||
``username`` and ``password`` keys to be valid.
|
||||
platform (str): Platform in the format ``os[/arch[/variant]]``
|
||||
all_tags (bool): Pull all image tags
|
||||
|
||||
Returns:
|
||||
(:py:class:`Image` or list): The image that has been pulled.
|
||||
If ``all_tags`` is True, 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')
|
||||
|
||||
>>> # Pull all tags in the busybox repo
|
||||
>>> images = client.images.pull('busybox', all_tags=True)
|
||||
"""
|
||||
repository, image_tag = parse_repository_tag(repository)
|
||||
tag = tag or image_tag or 'latest'
|
||||
|
||||
if 'stream' in kwargs:
|
||||
warnings.warn(
|
||||
'`stream` is not a valid parameter for this method'
|
||||
' and will be overridden',
|
||||
stacklevel=1,
|
||||
)
|
||||
del kwargs['stream']
|
||||
|
||||
pull_log = self.client.api.pull(
|
||||
repository, tag=tag, stream=True, all_tags=all_tags, **kwargs
|
||||
)
|
||||
for _ in pull_log:
|
||||
# We don't do anything with the logs, but we need
|
||||
# to keep the connection alive and wait for the image
|
||||
# to be pulled.
|
||||
pass
|
||||
if not all_tags:
|
||||
sep = '@' if tag.startswith('sha256:') else ':'
|
||||
return self.get(f'{repository}{sep}{tag}')
|
||||
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 prune_builds(self, *args, **kwargs):
|
||||
return self.client.api.prune_builds(*args, **kwargs)
|
||||
prune_builds.__doc__ = APIClient.prune_builds.__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
|
||||
@@ -0,0 +1,218 @@
|
||||
from ..api import APIClient
|
||||
from ..utils import version_gte
|
||||
from .containers import Container
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
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.
|
||||
driver_opt (dict): A dictionary of options to provide to the
|
||||
network driver. Defaults to ``None``.
|
||||
|
||||
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 network 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` (str|list): format either ``"key"``, ``"key=value"``
|
||||
or a list of such.
|
||||
- ``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):
|
||||
return self.client.api.prune_networks(filters=filters)
|
||||
prune.__doc__ = APIClient.prune_networks.__doc__
|
||||
@@ -0,0 +1,107 @@
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
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)
|
||||
]
|
||||
@@ -0,0 +1,206 @@
|
||||
from .. import errors
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
class Plugin(Model):
|
||||
"""
|
||||
A plugin on the server.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return f"<{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, force=False):
|
||||
"""
|
||||
Disable the plugin.
|
||||
|
||||
Args:
|
||||
force (bool): Force disable. Default: False
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
|
||||
self.client.api.disable_plugin(self.name, force)
|
||||
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)
|
||||
yield from self.client.api.upgrade_plugin(
|
||||
self.name,
|
||||
remote,
|
||||
privileges,
|
||||
)
|
||||
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]
|
||||
@@ -0,0 +1,92 @@
|
||||
class Model:
|
||||
"""
|
||||
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 f"<{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(f"{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 12 characters.
|
||||
"""
|
||||
return self.id[:12]
|
||||
|
||||
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:
|
||||
"""
|
||||
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(
|
||||
f"'{self.__class__.__name__}' object is not callable. "
|
||||
"You might be trying to use the old (pre-2.0) API - "
|
||||
"use docker.APIClient if so."
|
||||
)
|
||||
|
||||
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(f"Can't create {self.model.__name__} from {attrs}")
|
||||
@@ -0,0 +1,70 @@
|
||||
from ..api import APIClient
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
class Secret(Model):
|
||||
"""A secret."""
|
||||
id_attribute = 'ID'
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{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)
|
||||
obj.setdefault("Spec", {})["Name"] = kwargs.get("name")
|
||||
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]
|
||||
@@ -0,0 +1,390 @@
|
||||
import copy
|
||||
|
||||
from docker.errors import InvalidArgument, create_unexpected_kwargs_error
|
||||
from docker.types import ContainerSpec, Placement, ServiceMode, TaskTemplate
|
||||
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
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:
|
||||
bool: ``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,
|
||||
mode=service_mode,
|
||||
fetch_current_spec=True)
|
||||
|
||||
def force_update(self):
|
||||
"""
|
||||
Force update the service even if no changes require it.
|
||||
|
||||
Returns:
|
||||
bool: ``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): :py:class:`~docker.types.Placement`
|
||||
constraints.
|
||||
preferences (list of tuple): :py:class:`~docker.types.Placement`
|
||||
preferences.
|
||||
maxreplicas (int): :py:class:`~docker.types.Placement` maxreplicas
|
||||
or (int) representing maximum number of replicas per node.
|
||||
platforms (list of tuple): A list of platform constraints
|
||||
expressed as ``(arch, os)`` tuples.
|
||||
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.
|
||||
init (boolean): Run an init inside the container that forwards
|
||||
signals and reaps processes
|
||||
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 (:py:class:`list`): List of network names or IDs or
|
||||
:py:class:`~docker.types.NetworkAttachmentConfig` 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``
|
||||
rollback_config (RollbackConfig): Specification for the rollback
|
||||
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:`~docker.types.ConfigReference` that will be exposed
|
||||
to the service.
|
||||
privileges (Privileges): Security options for the service's
|
||||
containers.
|
||||
cap_add (:py:class:`list`): A list of kernel capabilities to add to
|
||||
the default set for the container.
|
||||
cap_drop (:py:class:`list`): A list of kernel capabilities to drop
|
||||
from the default set for the container.
|
||||
sysctls (:py:class:`dict`): A dict of sysctl values to add to the
|
||||
container
|
||||
|
||||
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``.
|
||||
status (bool): Include the service task count of running and
|
||||
desired tasks. 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',
|
||||
'cap_add',
|
||||
'cap_drop',
|
||||
'command',
|
||||
'configs',
|
||||
'dns_config',
|
||||
'env',
|
||||
'groups',
|
||||
'healthcheck',
|
||||
'hostname',
|
||||
'hosts',
|
||||
'image',
|
||||
'init',
|
||||
'isolation',
|
||||
'labels',
|
||||
'mounts',
|
||||
'open_stdin',
|
||||
'privileges',
|
||||
'read_only',
|
||||
'secrets',
|
||||
'stop_grace_period',
|
||||
'stop_signal',
|
||||
'tty',
|
||||
'user',
|
||||
'workdir',
|
||||
'sysctls',
|
||||
]
|
||||
|
||||
# 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',
|
||||
'rollback_config',
|
||||
'endpoint_spec',
|
||||
]
|
||||
|
||||
PLACEMENT_KWARGS = [
|
||||
'constraints',
|
||||
'preferences',
|
||||
'platforms',
|
||||
'maxreplicas',
|
||||
]
|
||||
|
||||
|
||||
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')
|
||||
|
||||
placement = {}
|
||||
for key in copy.copy(kwargs):
|
||||
if key in PLACEMENT_KWARGS:
|
||||
placement[key] = kwargs.pop(key)
|
||||
placement = Placement(**placement)
|
||||
task_template_kwargs['placement'] = placement
|
||||
|
||||
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
|
||||
@@ -0,0 +1,190 @@
|
||||
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().__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, default_addr_pool=None,
|
||||
subnet_size=None, data_path_addr=None, data_path_port=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
|
||||
default_addr_pool (list of str): Default Address Pool specifies
|
||||
default subnet pools for global scope networks. Each pool
|
||||
should be specified as a CIDR block, like '10.0.0.0/8'.
|
||||
Default: None
|
||||
subnet_size (int): SubnetSize specifies the subnet size of the
|
||||
networks created from the default subnet pool. Default: None
|
||||
data_path_addr (string): Address or interface to use for data path
|
||||
traffic. For example, 192.168.1.1, or an interface, like eth0.
|
||||
data_path_port (int): Port number to use for data path traffic.
|
||||
Acceptable port range is 1024 to 49151. If set to ``None`` or
|
||||
0, the default port 4789 will be used. Default: None
|
||||
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:
|
||||
(str): The ID of the created node.
|
||||
|
||||
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, default_addr_pool=['10.20.0.0/16],
|
||||
subnet_size=24, 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,
|
||||
'default_addr_pool': default_addr_pool,
|
||||
'subnet_size': subnet_size,
|
||||
'data_path_addr': data_path_addr,
|
||||
'data_path_port': data_path_port,
|
||||
}
|
||||
init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs)
|
||||
node_id = self.client.api.init_swarm(**init_kwargs)
|
||||
self.reload()
|
||||
return node_id
|
||||
|
||||
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,
|
||||
rotate_manager_unlock_key=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``.
|
||||
rotate_manager_unlock_key (bool): Rotate the manager unlock key.
|
||||
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,
|
||||
rotate_manager_unlock_key=rotate_manager_unlock_key
|
||||
)
|
||||
@@ -0,0 +1,99 @@
|
||||
from ..api import APIClient
|
||||
from .resource import Collection, Model
|
||||
|
||||
|
||||
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__
|
||||
Reference in New Issue
Block a user