diff options
| author | Joffrey F <joffrey@docker.com> | 2016-06-24 15:04:13 -0700 |
|---|---|---|
| committer | Joffrey F <joffrey@docker.com> | 2016-08-17 12:21:34 -0700 |
| commit | 172e95d52fe4f9bc6146828b34fbed567aec3945 (patch) | |
| tree | 053ed83bbc331a3d68abd4c17d2388a1bc561438 | |
| parent | 0f47db7fcc4ae2b22500afdd6b029c557d86f5b1 (diff) | |
| download | docker-py-172e95d52fe4f9bc6146828b34fbed567aec3945.tar.gz | |
Swarm service API implementation
Signed-off-by: Joffrey F <joffrey@docker.com>
| -rw-r--r-- | docker/api/__init__.py | 6 | ||||
| -rw-r--r-- | docker/api/service.py | 232 | ||||
| -rw-r--r-- | docker/client.py | 5 | ||||
| -rw-r--r-- | docker/utils/decorators.py | 2 |
4 files changed, 241 insertions, 4 deletions
diff --git a/docker/api/__init__.py b/docker/api/__init__.py index b0d6087..3c74677 100644 --- a/docker/api/__init__.py +++ b/docker/api/__init__.py @@ -4,6 +4,10 @@ from .container import ContainerApiMixin from .daemon import DaemonApiMixin from .exec_api import ExecApiMixin from .image import ImageApiMixin -from .volume import VolumeApiMixin from .network import NetworkApiMixin +from .service import ( + ServiceApiMixin, TaskTemplate, ContainerSpec, Mount, Resources, + RestartPolicy, UpdateConfig +) from .swarm import SwarmApiMixin +from .volume import VolumeApiMixin diff --git a/docker/api/service.py b/docker/api/service.py new file mode 100644 index 0000000..4be9624 --- /dev/null +++ b/docker/api/service.py @@ -0,0 +1,232 @@ +import six + +from .. import errors +from .. import utils + + +class ServiceApiMixin(object): + @utils.minimum_version('1.24') + def services(self, filters=None): + params = { + 'filters': utils.convert_filters(filters) if filters else None + } + url = self._url('/services') + return self._result(self._get(url, params=params), True) + + @utils.minimum_version('1.24') + def create_service( + self, task_config, name=None, labels=None, mode=None, + update_config=None, networks=None, endpoint_config=None + ): + url = self._url('/services/create') + data = { + 'Name': name, + 'Labels': labels, + 'TaskTemplate': task_config, + 'Mode': mode, + 'UpdateConfig': update_config, + 'Networks': networks, + 'Endpoint': endpoint_config + } + return self._result(self._post_json(url, data=data), True) + + @utils.minimum_version('1.24') + @utils.check_resource + def inspect_service(self, service): + url = self._url('/services/{0}', service) + return self._result(self._get(url), True) + + @utils.minimum_version('1.24') + @utils.check_resource + def remove_service(self, service): + url = self._url('/services/{0}', service) + resp = self._delete(url) + self._raise_for_status(resp) + + @utils.minimum_version('1.24') + @utils.check_resource + def update_service(self, service, task_template=None, name=None, + labels=None, mode=None, update_config=None, + networks=None, endpoint_config=None): + url = self._url('/services/{0}/update', service) + data = {} + if name is not None: + data['Name'] = name + if labels is not None: + data['Labels'] = labels + if mode is not None: + data['Mode'] = mode + if task_template is not None: + data['TaskTemplate'] = task_template + if update_config is not None: + data['UpdateConfig'] = update_config + if networks is not None: + data['Networks'] = networks + if endpoint_config is not None: + data['Endpoint'] = endpoint_config + + return self._result(self._post_json(url, data=data), True) + + +class TaskTemplate(dict): + def __init__(self, container_spec, resources=None, restart_policy=None, + placement=None, log_driver=None): + self['ContainerSpec'] = container_spec + if resources: + self['Resources'] = resources + if restart_policy: + self['RestartPolicy'] = restart_policy + if placement: + self['Placement'] = placement + if log_driver: + self['LogDriver'] = log_driver + + @property + def container_spec(self): + return self.get('ContainerSpec') + + @property + def resources(self): + return self.get('Resources') + + @property + def restart_policy(self): + return self.get('RestartPolicy') + + @property + def placement(self): + return self.get('Placement') + + +class ContainerSpec(dict): + def __init__(self, image, command=None, args=None, env=None, workdir=None, + user=None, labels=None, mounts=None, stop_grace_period=None): + self['Image'] = image + self['Command'] = command + self['Args'] = args + + if env is not None: + self['Env'] = env + if workdir is not None: + self['Dir'] = workdir + if user is not None: + self['User'] = user + if labels is not None: + self['Labels'] = labels + if mounts is not None: + for mount in mounts: + if isinstance(mount, six.string_types): + mounts.append(Mount.parse_mount_string(mount)) + mounts.remove(mount) + self['Mounts'] = mounts + if stop_grace_period is not None: + self['StopGracePeriod'] = stop_grace_period + + +class Mount(dict): + def __init__(self, target, source, type='volume', read_only=False, + propagation=None, no_copy=False, labels=None, + driver_config=None): + self['Target'] = target + self['Source'] = source + if type not in ('bind', 'volume'): + raise errors.DockerError( + 'Only acceptable mount types are `bind` and `volume`.' + ) + self['Type'] = type + + if type == 'bind': + if propagation is not None: + self['BindOptions'] = { + 'Propagation': propagation + } + if any(labels, driver_config, no_copy): + raise errors.DockerError( + 'Mount type is binding but volume options have been ' + 'provided.' + ) + else: + volume_opts = {} + if no_copy: + volume_opts['NoCopy'] = True + if labels: + volume_opts['Labels'] = labels + if driver_config: + volume_opts['driver_config'] = driver_config + if volume_opts: + self['VolumeOptions'] = volume_opts + if propagation: + raise errors.DockerError( + 'Mount type is volume but `propagation` argument has been ' + 'provided.' + ) + + @classmethod + def parse_mount_string(cls, string): + parts = string.split(':') + if len(parts) > 3: + raise errors.DockerError( + 'Invalid mount format "{0}"'.format(string) + ) + if len(parts) == 1: + return cls(target=parts[0]) + else: + target = parts[1] + source = parts[0] + read_only = not (len(parts) == 3 or parts[2] == 'ro') + return cls(target, source, read_only=read_only) + + +class Resources(dict): + def __init__(self, cpu_limit=None, mem_limit=None, cpu_reservation=None, + mem_reservation=None): + limits = {} + reservation = {} + if cpu_limit is not None: + limits['NanoCPUs'] = cpu_limit + if mem_limit is not None: + limits['MemoryBytes'] = mem_limit + if cpu_reservation is not None: + reservation['NanoCPUs'] = cpu_reservation + if mem_reservation is not None: + reservation['MemoryBytes'] = mem_reservation + + self['Limits'] = limits + self['Reservation'] = reservation + + +class UpdateConfig(dict): + def __init__(self, parallelism=0, delay=None, failure_action='continue'): + self['Parallelism'] = parallelism + if delay is not None: + self['Delay'] = delay + if failure_action not in ('pause', 'continue'): + raise errors.DockerError( + 'failure_action must be either `pause` or `continue`.' + ) + self['FailureAction'] = failure_action + + +class RestartConditionTypesEnum(object): + _values = ( + 'none', + 'on_failure', + 'any', + ) + NONE, ON_FAILURE, ANY = _values + + +class RestartPolicy(dict): + condition_types = RestartConditionTypesEnum + + def __init__(self, condition=RestartConditionTypesEnum.NONE, delay=0, + attempts=0, window=0): + if condition not in self.condition_types._values: + raise TypeError( + 'Invalid RestartPolicy condition {0}'.format(condition) + ) + + self['Condition'] = condition + self['Delay'] = delay + self['Attempts'] = attempts + self['Window'] = window diff --git a/docker/client.py b/docker/client.py index 9f75ce7..6c1e189 100644 --- a/docker/client.py +++ b/docker/client.py @@ -48,9 +48,10 @@ class Client( api.DaemonApiMixin, api.ExecApiMixin, api.ImageApiMixin, - api.VolumeApiMixin, api.NetworkApiMixin, - api.SwarmApiMixin): + api.ServiceApiMixin, + api.SwarmApiMixin, + api.VolumeApiMixin): def __init__(self, base_url=None, version=None, timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False, user_agent=constants.DEFAULT_USER_AGENT): diff --git a/docker/utils/decorators.py b/docker/utils/decorators.py index 46c28a8..2fe880c 100644 --- a/docker/utils/decorators.py +++ b/docker/utils/decorators.py @@ -13,7 +13,7 @@ def check_resource(f): elif kwargs.get('image'): resource_id = kwargs.pop('image') if isinstance(resource_id, dict): - resource_id = resource_id.get('Id') + resource_id = resource_id.get('Id', resource_id.get('ID')) if not resource_id: raise errors.NullResource( 'image or container param is undefined' |
