Skip to content

Commit 8cfd4cb

Browse files
authored
Merge pull request #1807 from rycus86/update_service_from_prev_spec
Update service from prev spec
2 parents 644a825 + a66c892 commit 8cfd4cb

File tree

6 files changed

+659
-21
lines changed

6 files changed

+659
-21
lines changed

docker/api/service.py

Lines changed: 63 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,21 @@ def raise_version_error(param, min_version):
6666
raise_version_error('ContainerSpec.privileges', '1.30')
6767

6868

69+
def _merge_task_template(current, override):
70+
merged = current.copy()
71+
if override is not None:
72+
for ts_key, ts_value in override.items():
73+
if ts_key == 'ContainerSpec':
74+
if 'ContainerSpec' not in merged:
75+
merged['ContainerSpec'] = {}
76+
for cs_key, cs_value in override['ContainerSpec'].items():
77+
if cs_value is not None:
78+
merged['ContainerSpec'][cs_key] = cs_value
79+
elif ts_value is not None:
80+
merged[ts_key] = ts_value
81+
return merged
82+
83+
6984
class ServiceApiMixin(object):
7085
@utils.minimum_version('1.24')
7186
def create_service(
@@ -310,7 +325,7 @@ def tasks(self, filters=None):
310325
def update_service(self, service, version, task_template=None, name=None,
311326
labels=None, mode=None, update_config=None,
312327
networks=None, endpoint_config=None,
313-
endpoint_spec=None):
328+
endpoint_spec=None, fetch_current_spec=False):
314329
"""
315330
Update a service.
316331
@@ -332,6 +347,8 @@ def update_service(self, service, version, task_template=None, name=None,
332347
the service to. Default: ``None``.
333348
endpoint_spec (EndpointSpec): Properties that can be configured to
334349
access and load balance a service. Default: ``None``.
350+
fetch_current_spec (boolean): Use the undefined settings from the
351+
current specification of the service. Default: ``False``
335352
336353
Returns:
337354
``True`` if successful.
@@ -349,32 +366,64 @@ def update_service(self, service, version, task_template=None, name=None,
349366

350367
_check_api_features(self._version, task_template, update_config)
351368

369+
if fetch_current_spec:
370+
inspect_defaults = True
371+
if utils.version_lt(self._version, '1.29'):
372+
inspect_defaults = None
373+
current = self.inspect_service(
374+
service, insert_defaults=inspect_defaults
375+
)['Spec']
376+
377+
else:
378+
current = {}
379+
352380
url = self._url('/services/{0}/update', service)
353381
data = {}
354382
headers = {}
355-
if name is not None:
356-
data['Name'] = name
357-
if labels is not None:
358-
data['Labels'] = labels
383+
384+
data['Name'] = current.get('Name') if name is None else name
385+
386+
data['Labels'] = current.get('Labels') if labels is None else labels
387+
359388
if mode is not None:
360389
if not isinstance(mode, dict):
361390
mode = ServiceMode(mode)
362391
data['Mode'] = mode
363-
if task_template is not None:
364-
image = task_template.get('ContainerSpec', {}).get('Image', None)
365-
if image is not None:
366-
registry, repo_name = auth.resolve_repository_name(image)
367-
auth_header = auth.get_config_header(self, registry)
368-
if auth_header:
369-
headers['X-Registry-Auth'] = auth_header
370-
data['TaskTemplate'] = task_template
392+
else:
393+
data['Mode'] = current.get('Mode')
394+
395+
data['TaskTemplate'] = _merge_task_template(
396+
current.get('TaskTemplate', {}), task_template
397+
)
398+
399+
container_spec = data['TaskTemplate'].get('ContainerSpec', {})
400+
image = container_spec.get('Image', None)
401+
if image is not None:
402+
registry, repo_name = auth.resolve_repository_name(image)
403+
auth_header = auth.get_config_header(self, registry)
404+
if auth_header:
405+
headers['X-Registry-Auth'] = auth_header
406+
371407
if update_config is not None:
372408
data['UpdateConfig'] = update_config
409+
else:
410+
data['UpdateConfig'] = current.get('UpdateConfig')
373411

374412
if networks is not None:
375-
data['Networks'] = utils.convert_service_networks(networks)
413+
converted_networks = utils.convert_service_networks(networks)
414+
data['TaskTemplate']['Networks'] = converted_networks
415+
elif data['TaskTemplate'].get('Networks') is None:
416+
current_task_template = current.get('TaskTemplate', {})
417+
current_networks = current_task_template.get('Networks')
418+
if current_networks is None:
419+
current_networks = current.get('Networks')
420+
if current_networks is not None:
421+
data['TaskTemplate']['Networks'] = current_networks
422+
376423
if endpoint_spec is not None:
377424
data['EndpointSpec'] = endpoint_spec
425+
else:
426+
data['EndpointSpec'] = current.get('EndpointSpec')
378427

379428
resp = self._post_json(
380429
url, data=data, params={'version': version}, headers=headers

docker/models/services.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ def list(self, **kwargs):
251251

252252
# kwargs to copy straight over to TaskTemplate
253253
TASK_TEMPLATE_KWARGS = [
254+
'networks',
254255
'resources',
255256
'restart_policy',
256257
]
@@ -261,7 +262,6 @@ def list(self, **kwargs):
261262
'labels',
262263
'mode',
263264
'update_config',
264-
'networks',
265265
'endpoint_spec',
266266
]
267267

@@ -295,6 +295,15 @@ def _get_create_service_kwargs(func_name, kwargs):
295295
'Options': kwargs.pop('log_driver_options', {})
296296
}
297297

298+
if func_name == 'update':
299+
if 'force_update' in kwargs:
300+
task_template_kwargs['force_update'] = kwargs.pop('force_update')
301+
302+
# fetch the current spec by default if updating the service
303+
# through the model
304+
fetch_current_spec = kwargs.pop('fetch_current_spec', True)
305+
create_kwargs['fetch_current_spec'] = fetch_current_spec
306+
298307
# All kwargs should have been consumed by this point, so raise
299308
# error if any are left
300309
if kwargs:

docker/types/services.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..constants import IS_WINDOWS_PLATFORM
55
from ..utils import (
66
check_resource, format_environment, format_extra_hosts, parse_bytes,
7-
split_command,
7+
split_command, convert_service_networks,
88
)
99

1010

@@ -26,11 +26,14 @@ class TaskTemplate(dict):
2626
placement (Placement): Placement instructions for the scheduler.
2727
If a list is passed instead, it is assumed to be a list of
2828
constraints as part of a :py:class:`Placement` object.
29+
networks (:py:class:`list`): List of network names or IDs to attach
30+
the containers to.
2931
force_update (int): A counter that triggers an update even if no
3032
relevant parameters have been changed.
3133
"""
3234
def __init__(self, container_spec, resources=None, restart_policy=None,
33-
placement=None, log_driver=None, force_update=None):
35+
placement=None, log_driver=None, networks=None,
36+
force_update=None):
3437
self['ContainerSpec'] = container_spec
3538
if resources:
3639
self['Resources'] = resources
@@ -42,6 +45,8 @@ def __init__(self, container_spec, resources=None, restart_policy=None,
4245
self['Placement'] = placement
4346
if log_driver:
4447
self['LogDriver'] = log_driver
48+
if networks:
49+
self['Networks'] = convert_service_networks(networks)
4550

4651
if force_update is not None:
4752
if not isinstance(force_update, int):

0 commit comments

Comments
 (0)