first commit
This commit is contained in:
486
backend/venv/lib/python3.9/site-packages/docker/api/service.py
Normal file
486
backend/venv/lib/python3.9/site-packages/docker/api/service.py
Normal file
@@ -0,0 +1,486 @@
|
||||
from .. import auth, errors, utils
|
||||
from ..types import ServiceMode
|
||||
|
||||
|
||||
def _check_api_features(version, task_template, update_config, endpoint_spec,
|
||||
rollback_config):
|
||||
|
||||
def raise_version_error(param, min_version):
|
||||
raise errors.InvalidVersion(
|
||||
f'{param} is not supported in API version < {min_version}'
|
||||
)
|
||||
|
||||
if update_config is not None:
|
||||
if utils.version_lt(version, '1.25'):
|
||||
if 'MaxFailureRatio' in update_config:
|
||||
raise_version_error('UpdateConfig.max_failure_ratio', '1.25')
|
||||
if 'Monitor' in update_config:
|
||||
raise_version_error('UpdateConfig.monitor', '1.25')
|
||||
|
||||
if utils.version_lt(version, '1.28'):
|
||||
if update_config.get('FailureAction') == 'rollback':
|
||||
raise_version_error(
|
||||
'UpdateConfig.failure_action rollback', '1.28'
|
||||
)
|
||||
|
||||
if utils.version_lt(version, '1.29'):
|
||||
if 'Order' in update_config:
|
||||
raise_version_error('UpdateConfig.order', '1.29')
|
||||
|
||||
if rollback_config is not None:
|
||||
if utils.version_lt(version, '1.28'):
|
||||
raise_version_error('rollback_config', '1.28')
|
||||
|
||||
if utils.version_lt(version, '1.29'):
|
||||
if 'Order' in update_config:
|
||||
raise_version_error('RollbackConfig.order', '1.29')
|
||||
|
||||
if endpoint_spec is not None:
|
||||
if utils.version_lt(version, '1.32') and 'Ports' in endpoint_spec:
|
||||
if any(p.get('PublishMode') for p in endpoint_spec['Ports']):
|
||||
raise_version_error('EndpointSpec.Ports[].mode', '1.32')
|
||||
|
||||
if task_template is not None:
|
||||
if 'ForceUpdate' in task_template and utils.version_lt(
|
||||
version, '1.25'):
|
||||
raise_version_error('force_update', '1.25')
|
||||
|
||||
if task_template.get('Placement'):
|
||||
if utils.version_lt(version, '1.30'):
|
||||
if task_template['Placement'].get('Platforms'):
|
||||
raise_version_error('Placement.platforms', '1.30')
|
||||
if utils.version_lt(version, '1.27'):
|
||||
if task_template['Placement'].get('Preferences'):
|
||||
raise_version_error('Placement.preferences', '1.27')
|
||||
|
||||
if task_template.get('ContainerSpec'):
|
||||
container_spec = task_template.get('ContainerSpec')
|
||||
|
||||
if utils.version_lt(version, '1.25'):
|
||||
if container_spec.get('TTY'):
|
||||
raise_version_error('ContainerSpec.tty', '1.25')
|
||||
if container_spec.get('Hostname') is not None:
|
||||
raise_version_error('ContainerSpec.hostname', '1.25')
|
||||
if container_spec.get('Hosts') is not None:
|
||||
raise_version_error('ContainerSpec.hosts', '1.25')
|
||||
if container_spec.get('Groups') is not None:
|
||||
raise_version_error('ContainerSpec.groups', '1.25')
|
||||
if container_spec.get('DNSConfig') is not None:
|
||||
raise_version_error('ContainerSpec.dns_config', '1.25')
|
||||
if container_spec.get('Healthcheck') is not None:
|
||||
raise_version_error('ContainerSpec.healthcheck', '1.25')
|
||||
|
||||
if utils.version_lt(version, '1.28'):
|
||||
if container_spec.get('ReadOnly') is not None:
|
||||
raise_version_error('ContainerSpec.dns_config', '1.28')
|
||||
if container_spec.get('StopSignal') is not None:
|
||||
raise_version_error('ContainerSpec.stop_signal', '1.28')
|
||||
|
||||
if utils.version_lt(version, '1.30'):
|
||||
if container_spec.get('Configs') is not None:
|
||||
raise_version_error('ContainerSpec.configs', '1.30')
|
||||
if container_spec.get('Privileges') is not None:
|
||||
raise_version_error('ContainerSpec.privileges', '1.30')
|
||||
|
||||
if utils.version_lt(version, '1.35'):
|
||||
if container_spec.get('Isolation') is not None:
|
||||
raise_version_error('ContainerSpec.isolation', '1.35')
|
||||
|
||||
if utils.version_lt(version, '1.38'):
|
||||
if container_spec.get('Init') is not None:
|
||||
raise_version_error('ContainerSpec.init', '1.38')
|
||||
|
||||
if task_template.get('Resources'):
|
||||
if utils.version_lt(version, '1.32'):
|
||||
if task_template['Resources'].get('GenericResources'):
|
||||
raise_version_error('Resources.generic_resources', '1.32')
|
||||
|
||||
|
||||
def _merge_task_template(current, override):
|
||||
merged = current.copy()
|
||||
if override is not None:
|
||||
for ts_key, ts_value in override.items():
|
||||
if ts_key == 'ContainerSpec':
|
||||
if 'ContainerSpec' not in merged:
|
||||
merged['ContainerSpec'] = {}
|
||||
for cs_key, cs_value in override['ContainerSpec'].items():
|
||||
if cs_value is not None:
|
||||
merged['ContainerSpec'][cs_key] = cs_value
|
||||
elif ts_value is not None:
|
||||
merged[ts_key] = ts_value
|
||||
return merged
|
||||
|
||||
|
||||
class ServiceApiMixin:
|
||||
@utils.minimum_version('1.24')
|
||||
def create_service(
|
||||
self, task_template, name=None, labels=None, mode=None,
|
||||
update_config=None, networks=None, endpoint_config=None,
|
||||
endpoint_spec=None, rollback_config=None
|
||||
):
|
||||
"""
|
||||
Create a service.
|
||||
|
||||
Args:
|
||||
task_template (TaskTemplate): Specification of the task to start as
|
||||
part of the new service.
|
||||
name (string): User-defined name for the service. Optional.
|
||||
labels (dict): A map of labels to associate with the service.
|
||||
Optional.
|
||||
mode (ServiceMode): Scheduling mode for the service (replicated
|
||||
or global). Defaults to replicated.
|
||||
update_config (UpdateConfig): Specification for the update strategy
|
||||
of the service. Default: ``None``
|
||||
rollback_config (RollbackConfig): Specification for the rollback
|
||||
strategy of the service. Default: ``None``
|
||||
networks (:py:class:`list`): List of network names or IDs or
|
||||
:py:class:`~docker.types.NetworkAttachmentConfig` to attach the
|
||||
service to. Default: ``None``.
|
||||
endpoint_spec (EndpointSpec): Properties that can be configured to
|
||||
access and load balance a service. Default: ``None``.
|
||||
|
||||
Returns:
|
||||
A dictionary containing an ``ID`` key for the newly created
|
||||
service.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
|
||||
_check_api_features(
|
||||
self._version, task_template, update_config, endpoint_spec,
|
||||
rollback_config
|
||||
)
|
||||
|
||||
url = self._url('/services/create')
|
||||
headers = {}
|
||||
image = task_template.get('ContainerSpec', {}).get('Image', None)
|
||||
if image is None:
|
||||
raise errors.DockerException(
|
||||
'Missing mandatory Image key in ContainerSpec'
|
||||
)
|
||||
if mode and not isinstance(mode, dict):
|
||||
mode = ServiceMode(mode)
|
||||
|
||||
registry, repo_name = auth.resolve_repository_name(image)
|
||||
auth_header = auth.get_config_header(self, registry)
|
||||
if auth_header:
|
||||
headers['X-Registry-Auth'] = auth_header
|
||||
if utils.version_lt(self._version, '1.25'):
|
||||
networks = networks or task_template.pop('Networks', None)
|
||||
data = {
|
||||
'Name': name,
|
||||
'Labels': labels,
|
||||
'TaskTemplate': task_template,
|
||||
'Mode': mode,
|
||||
'Networks': utils.convert_service_networks(networks),
|
||||
'EndpointSpec': endpoint_spec
|
||||
}
|
||||
|
||||
if update_config is not None:
|
||||
data['UpdateConfig'] = update_config
|
||||
|
||||
if rollback_config is not None:
|
||||
data['RollbackConfig'] = rollback_config
|
||||
|
||||
return self._result(
|
||||
self._post_json(url, data=data, headers=headers), True
|
||||
)
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
@utils.check_resource('service')
|
||||
def inspect_service(self, service, insert_defaults=None):
|
||||
"""
|
||||
Return information about a service.
|
||||
|
||||
Args:
|
||||
service (str): Service name or ID.
|
||||
insert_defaults (boolean): If true, default values will be merged
|
||||
into the service inspect output.
|
||||
|
||||
Returns:
|
||||
(dict): A dictionary of the server-side representation of the
|
||||
service, including all relevant properties.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
url = self._url('/services/{0}', service)
|
||||
params = {}
|
||||
if insert_defaults is not None:
|
||||
if utils.version_lt(self._version, '1.29'):
|
||||
raise errors.InvalidVersion(
|
||||
'insert_defaults is not supported in API version < 1.29'
|
||||
)
|
||||
params['insertDefaults'] = insert_defaults
|
||||
|
||||
return self._result(self._get(url, params=params), True)
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
@utils.check_resource('task')
|
||||
def inspect_task(self, task):
|
||||
"""
|
||||
Retrieve information about a task.
|
||||
|
||||
Args:
|
||||
task (str): Task ID
|
||||
|
||||
Returns:
|
||||
(dict): Information about the task.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
url = self._url('/tasks/{0}', task)
|
||||
return self._result(self._get(url), True)
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
@utils.check_resource('service')
|
||||
def remove_service(self, service):
|
||||
"""
|
||||
Stop and remove a service.
|
||||
|
||||
Args:
|
||||
service (str): Service name or ID
|
||||
|
||||
Returns:
|
||||
``True`` if successful.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
|
||||
url = self._url('/services/{0}', service)
|
||||
resp = self._delete(url)
|
||||
self._raise_for_status(resp)
|
||||
return True
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
def services(self, filters=None, status=None):
|
||||
"""
|
||||
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:
|
||||
A list of dictionaries containing data about each service.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
params = {
|
||||
'filters': utils.convert_filters(filters) if filters else None
|
||||
}
|
||||
if status is not None:
|
||||
if utils.version_lt(self._version, '1.41'):
|
||||
raise errors.InvalidVersion(
|
||||
'status is not supported in API version < 1.41'
|
||||
)
|
||||
params['status'] = status
|
||||
url = self._url('/services')
|
||||
return self._result(self._get(url, params=params), True)
|
||||
|
||||
@utils.minimum_version('1.25')
|
||||
@utils.check_resource('service')
|
||||
def service_logs(self, service, details=False, follow=False, stdout=False,
|
||||
stderr=False, since=0, timestamps=False, tail='all',
|
||||
is_tty=None):
|
||||
"""
|
||||
Get log stream for a service.
|
||||
Note: This endpoint works only for services with the ``json-file``
|
||||
or ``journald`` logging drivers.
|
||||
|
||||
Args:
|
||||
service (str): ID or name of the service
|
||||
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``
|
||||
is_tty (bool): Whether the service's :py:class:`ContainerSpec`
|
||||
enables the TTY option. If omitted, the method will query
|
||||
the Engine for the information, causing an additional
|
||||
roundtrip.
|
||||
|
||||
Returns (generator): Logs for the service.
|
||||
"""
|
||||
params = {
|
||||
'details': details,
|
||||
'follow': follow,
|
||||
'stdout': stdout,
|
||||
'stderr': stderr,
|
||||
'since': since,
|
||||
'timestamps': timestamps,
|
||||
'tail': tail
|
||||
}
|
||||
|
||||
url = self._url('/services/{0}/logs', service)
|
||||
res = self._get(url, params=params, stream=True)
|
||||
if is_tty is None:
|
||||
is_tty = self.inspect_service(
|
||||
service
|
||||
)['Spec']['TaskTemplate']['ContainerSpec'].get('TTY', False)
|
||||
return self._get_result_tty(True, res, is_tty)
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
def tasks(self, filters=None):
|
||||
"""
|
||||
Retrieve a list of tasks.
|
||||
|
||||
Args:
|
||||
filters (dict): A map of filters to process on the tasks list.
|
||||
Valid filters: ``id``, ``name``, ``service``, ``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.
|
||||
"""
|
||||
|
||||
params = {
|
||||
'filters': utils.convert_filters(filters) if filters else None
|
||||
}
|
||||
url = self._url('/tasks')
|
||||
return self._result(self._get(url, params=params), True)
|
||||
|
||||
@utils.minimum_version('1.24')
|
||||
@utils.check_resource('service')
|
||||
def update_service(self, service, version, task_template=None, name=None,
|
||||
labels=None, mode=None, update_config=None,
|
||||
networks=None, endpoint_config=None,
|
||||
endpoint_spec=None, fetch_current_spec=False,
|
||||
rollback_config=None):
|
||||
"""
|
||||
Update a service.
|
||||
|
||||
Args:
|
||||
service (string): A service identifier (either its name or service
|
||||
ID).
|
||||
version (int): The version number of the service object being
|
||||
updated. This is required to avoid conflicting writes.
|
||||
task_template (TaskTemplate): Specification of the updated task to
|
||||
start as part of the service.
|
||||
name (string): New name for the service. Optional.
|
||||
labels (dict): A map of labels to associate with the service.
|
||||
Optional.
|
||||
mode (ServiceMode): Scheduling mode for the service (replicated
|
||||
or global). Defaults to replicated.
|
||||
update_config (UpdateConfig): Specification for the update strategy
|
||||
of the service. Default: ``None``.
|
||||
rollback_config (RollbackConfig): Specification for the rollback
|
||||
strategy of the service. Default: ``None``
|
||||
networks (:py:class:`list`): List of network names or IDs or
|
||||
:py:class:`~docker.types.NetworkAttachmentConfig` to attach the
|
||||
service to. Default: ``None``.
|
||||
endpoint_spec (EndpointSpec): Properties that can be configured to
|
||||
access and load balance a service. Default: ``None``.
|
||||
fetch_current_spec (boolean): Use the undefined settings from the
|
||||
current specification of the service. Default: ``False``
|
||||
|
||||
Returns:
|
||||
A dictionary containing a ``Warnings`` key.
|
||||
|
||||
Raises:
|
||||
:py:class:`docker.errors.APIError`
|
||||
If the server returns an error.
|
||||
"""
|
||||
|
||||
_check_api_features(
|
||||
self._version, task_template, update_config, endpoint_spec,
|
||||
rollback_config
|
||||
)
|
||||
|
||||
if fetch_current_spec:
|
||||
inspect_defaults = True
|
||||
if utils.version_lt(self._version, '1.29'):
|
||||
inspect_defaults = None
|
||||
current = self.inspect_service(
|
||||
service, insert_defaults=inspect_defaults
|
||||
)['Spec']
|
||||
|
||||
else:
|
||||
current = {}
|
||||
|
||||
url = self._url('/services/{0}/update', service)
|
||||
data = {}
|
||||
headers = {}
|
||||
|
||||
data['Name'] = current.get('Name') if name is None else name
|
||||
|
||||
data['Labels'] = current.get('Labels') if labels is None else labels
|
||||
|
||||
if mode is not None:
|
||||
if not isinstance(mode, dict):
|
||||
mode = ServiceMode(mode)
|
||||
data['Mode'] = mode
|
||||
else:
|
||||
data['Mode'] = current.get('Mode')
|
||||
|
||||
data['TaskTemplate'] = _merge_task_template(
|
||||
current.get('TaskTemplate', {}), task_template
|
||||
)
|
||||
|
||||
container_spec = data['TaskTemplate'].get('ContainerSpec', {})
|
||||
image = container_spec.get('Image', None)
|
||||
if image is not None:
|
||||
registry, repo_name = auth.resolve_repository_name(image)
|
||||
auth_header = auth.get_config_header(self, registry)
|
||||
if auth_header:
|
||||
headers['X-Registry-Auth'] = auth_header
|
||||
|
||||
if update_config is not None:
|
||||
data['UpdateConfig'] = update_config
|
||||
else:
|
||||
data['UpdateConfig'] = current.get('UpdateConfig')
|
||||
|
||||
if rollback_config is not None:
|
||||
data['RollbackConfig'] = rollback_config
|
||||
else:
|
||||
data['RollbackConfig'] = current.get('RollbackConfig')
|
||||
|
||||
if networks is not None:
|
||||
converted_networks = utils.convert_service_networks(networks)
|
||||
if utils.version_lt(self._version, '1.25'):
|
||||
data['Networks'] = converted_networks
|
||||
else:
|
||||
data['TaskTemplate']['Networks'] = converted_networks
|
||||
elif utils.version_lt(self._version, '1.25'):
|
||||
data['Networks'] = current.get('Networks')
|
||||
elif data['TaskTemplate'].get('Networks') is None:
|
||||
current_task_template = current.get('TaskTemplate', {})
|
||||
current_networks = current_task_template.get('Networks')
|
||||
if current_networks is None:
|
||||
current_networks = current.get('Networks')
|
||||
if current_networks is not None:
|
||||
data['TaskTemplate']['Networks'] = current_networks
|
||||
|
||||
if endpoint_spec is not None:
|
||||
data['EndpointSpec'] = endpoint_spec
|
||||
else:
|
||||
data['EndpointSpec'] = current.get('EndpointSpec')
|
||||
|
||||
resp = self._post_json(
|
||||
url, data=data, params={'version': version}, headers=headers
|
||||
)
|
||||
return self._result(resp, json=True)
|
||||
Reference in New Issue
Block a user