first commit
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user