mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
docker_swarm_service: Add healthcheck option (#52419)
* Define yaml versions as strings * Add healthcheck option * Add changelog fragment * Don’t set version_added as strings * Use single quoted string * Disable healthcheck tests on python 2.6 * Bring back quoting on already quoted version-added * Python 2.6 compat * Move functions to docker-common * Move parse_healthcheck to docker-common * Cast exception to str before printing failure * Extend parse_healthcheck tests
This commit is contained in:
parent
77d116f66e
commit
18b968d486
7 changed files with 374 additions and 80 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- "docker_swarm_service - Added support for ``healthcheck`` parameter."
|
|
@ -18,8 +18,10 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from datetime import timedelta
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
from ansible.module_utils.basic import AnsibleModule, env_fallback
|
||||||
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
from ansible.module_utils.six.moves.urllib.parse import urlparse
|
||||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE
|
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE, BOOLEANS_FALSE
|
||||||
|
@ -833,3 +835,91 @@ def clean_dict_booleans_for_docker_api(data):
|
||||||
v = str(v)
|
v = str(v)
|
||||||
result[str(k)] = v
|
result[str(k)] = v
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def convert_duration_to_nanosecond(time_str):
|
||||||
|
"""
|
||||||
|
Return time duration in nanosecond.
|
||||||
|
"""
|
||||||
|
if not isinstance(time_str, str):
|
||||||
|
raise ValueError('Missing unit in duration - %s' % time_str)
|
||||||
|
|
||||||
|
regex = re.compile(
|
||||||
|
r'^(((?P<hours>\d+)h)?'
|
||||||
|
r'((?P<minutes>\d+)m(?!s))?'
|
||||||
|
r'((?P<seconds>\d+)s)?'
|
||||||
|
r'((?P<milliseconds>\d+)ms)?'
|
||||||
|
r'((?P<microseconds>\d+)us)?)$'
|
||||||
|
)
|
||||||
|
parts = regex.match(time_str)
|
||||||
|
|
||||||
|
if not parts:
|
||||||
|
raise ValueError('Invalid time duration - %s' % time_str)
|
||||||
|
|
||||||
|
parts = parts.groupdict()
|
||||||
|
time_params = {}
|
||||||
|
for (name, value) in parts.items():
|
||||||
|
if value:
|
||||||
|
time_params[name] = int(value)
|
||||||
|
|
||||||
|
delta = timedelta(**time_params)
|
||||||
|
time_in_nanoseconds = (
|
||||||
|
delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10 ** 6
|
||||||
|
) * 10 ** 3
|
||||||
|
|
||||||
|
return time_in_nanoseconds
|
||||||
|
|
||||||
|
|
||||||
|
def parse_healthcheck(healthcheck):
|
||||||
|
"""
|
||||||
|
Return dictionary of healthcheck parameters and boolean if
|
||||||
|
healthcheck defined in image was requested to be disabled.
|
||||||
|
"""
|
||||||
|
if (not healthcheck) or (not healthcheck.get('test')):
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
|
||||||
|
# All supported healthcheck parameters
|
||||||
|
options = dict(
|
||||||
|
test='test',
|
||||||
|
interval='interval',
|
||||||
|
timeout='timeout',
|
||||||
|
start_period='start_period',
|
||||||
|
retries='retries'
|
||||||
|
)
|
||||||
|
|
||||||
|
duration_options = ['interval', 'timeout', 'start_period']
|
||||||
|
|
||||||
|
for (key, value) in options.items():
|
||||||
|
if value in healthcheck:
|
||||||
|
if healthcheck.get(value) is None:
|
||||||
|
# due to recursive argument_spec, all keys are always present
|
||||||
|
# (but have default value None if not specified)
|
||||||
|
continue
|
||||||
|
if value in duration_options:
|
||||||
|
time = convert_duration_to_nanosecond(healthcheck.get(value))
|
||||||
|
if time:
|
||||||
|
result[key] = time
|
||||||
|
elif healthcheck.get(value):
|
||||||
|
result[key] = healthcheck.get(value)
|
||||||
|
if key == 'test':
|
||||||
|
if isinstance(result[key], (tuple, list)):
|
||||||
|
result[key] = [str(e) for e in result[key]]
|
||||||
|
else:
|
||||||
|
result[key] = ['CMD-SHELL', str(result[key])]
|
||||||
|
elif key == 'retries':
|
||||||
|
try:
|
||||||
|
result[key] = int(result[key])
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
'Cannot parse number of retries for healthcheck. '
|
||||||
|
'Expected an integer, got "{0}".'.format(result[key])
|
||||||
|
)
|
||||||
|
|
||||||
|
if result['test'] == ['NONE']:
|
||||||
|
# If the user explicitly disables the healthcheck, return None
|
||||||
|
# as the healthcheck object, and set disable_healthcheck to True
|
||||||
|
return None, True
|
||||||
|
|
||||||
|
return result, False
|
||||||
|
|
|
@ -863,14 +863,17 @@ docker_container:
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
from datetime import timedelta
|
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
from ansible.module_utils.basic import human_to_bytes
|
from ansible.module_utils.basic import human_to_bytes
|
||||||
from ansible.module_utils.docker.common import (
|
from ansible.module_utils.docker.common import (
|
||||||
AnsibleDockerClient,
|
AnsibleDockerClient,
|
||||||
DockerBaseClass, sanitize_result, is_image_name_id,
|
DifferenceTracker,
|
||||||
compare_generic, DifferenceTracker,
|
DockerBaseClass,
|
||||||
|
compare_generic,
|
||||||
|
is_image_name_id,
|
||||||
|
sanitize_result,
|
||||||
|
parse_healthcheck
|
||||||
)
|
)
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
@ -1081,7 +1084,11 @@ class TaskParameters(DockerBaseClass):
|
||||||
self.ulimits = self._parse_ulimits()
|
self.ulimits = self._parse_ulimits()
|
||||||
self.sysctls = self._parse_sysctls()
|
self.sysctls = self._parse_sysctls()
|
||||||
self.log_config = self._parse_log_config()
|
self.log_config = self._parse_log_config()
|
||||||
self.healthcheck, self.disable_healthcheck = self._parse_healthcheck()
|
try:
|
||||||
|
self.healthcheck, self.disable_healthcheck = parse_healthcheck(self.healthcheck)
|
||||||
|
except ValueError as e:
|
||||||
|
self.fail(str(e))
|
||||||
|
|
||||||
self.exp_links = None
|
self.exp_links = None
|
||||||
self.volume_binds = self._get_volume_binds(self.volumes)
|
self.volume_binds = self._get_volume_binds(self.volumes)
|
||||||
self.pid_mode = self._replace_container_names(self.pid_mode)
|
self.pid_mode = self._replace_container_names(self.pid_mode)
|
||||||
|
@ -1498,81 +1505,6 @@ class TaskParameters(DockerBaseClass):
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
self.fail('Error parsing logging options - %s' % (exc))
|
self.fail('Error parsing logging options - %s' % (exc))
|
||||||
|
|
||||||
def _parse_healthcheck(self):
|
|
||||||
'''
|
|
||||||
Return dictionary of healthcheck parameters
|
|
||||||
'''
|
|
||||||
if (not self.healthcheck) or (not self.healthcheck.get('test')):
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
result = dict()
|
|
||||||
|
|
||||||
# all the supported healthecheck parameters
|
|
||||||
options = dict(
|
|
||||||
test='test',
|
|
||||||
interval='interval',
|
|
||||||
timeout='timeout',
|
|
||||||
start_period='start_period',
|
|
||||||
retries='retries'
|
|
||||||
)
|
|
||||||
|
|
||||||
duration_options = ['interval', 'timeout', 'start_period']
|
|
||||||
|
|
||||||
for (key, value) in options.items():
|
|
||||||
if value in self.healthcheck:
|
|
||||||
if self.healthcheck.get(value) is None:
|
|
||||||
# due to recursive argument_spec, all keys are always present
|
|
||||||
# (but have default value None if not specified)
|
|
||||||
continue
|
|
||||||
if value in duration_options:
|
|
||||||
time = self._convert_duration_to_nanosecond(self.healthcheck.get(value))
|
|
||||||
if time:
|
|
||||||
result[key] = time
|
|
||||||
elif self.healthcheck.get(value):
|
|
||||||
result[key] = self.healthcheck.get(value)
|
|
||||||
if key == 'test':
|
|
||||||
if isinstance(result[key], (tuple, list)):
|
|
||||||
result[key] = [str(e) for e in result[key]]
|
|
||||||
else:
|
|
||||||
result[key] = ["CMD-SHELL", str(result[key])]
|
|
||||||
elif key == 'retries':
|
|
||||||
try:
|
|
||||||
result[key] = int(result[key])
|
|
||||||
except Exception as dummy:
|
|
||||||
self.fail('Cannot parse number of retries for healthcheck. '
|
|
||||||
'Expected an integer, got "{0}".'.format(result[key]))
|
|
||||||
|
|
||||||
if result['test'] == ['NONE']:
|
|
||||||
# If the user explicitly disables the healthcheck, return None
|
|
||||||
# as the healthcheck object, and set disable_healthcheck to True
|
|
||||||
return None, True
|
|
||||||
|
|
||||||
return result, False
|
|
||||||
|
|
||||||
def _convert_duration_to_nanosecond(self, time_str):
|
|
||||||
'''
|
|
||||||
Return time duration in nanosecond
|
|
||||||
'''
|
|
||||||
if not isinstance(time_str, str):
|
|
||||||
self.fail("Missing unit in duration - %s" % time_str)
|
|
||||||
|
|
||||||
regex = re.compile(r'^(((?P<hours>\d+)h)?((?P<minutes>\d+)m(?!s))?((?P<seconds>\d+)s)?((?P<milliseconds>\d+)ms)?((?P<microseconds>\d+)us)?)$')
|
|
||||||
parts = regex.match(time_str)
|
|
||||||
|
|
||||||
if not parts:
|
|
||||||
self.fail("Invalid time duration - %s" % time_str)
|
|
||||||
|
|
||||||
parts = parts.groupdict()
|
|
||||||
time_params = {}
|
|
||||||
for (name, value) in parts.items():
|
|
||||||
if value:
|
|
||||||
time_params[name] = int(value)
|
|
||||||
|
|
||||||
time = timedelta(**time_params)
|
|
||||||
time_in_nanoseconds = int(time.total_seconds() * 1000000000)
|
|
||||||
|
|
||||||
return time_in_nanoseconds
|
|
||||||
|
|
||||||
def _parse_tmpfs(self):
|
def _parse_tmpfs(self):
|
||||||
'''
|
'''
|
||||||
Turn tmpfs into a hash of Tmpfs objects
|
Turn tmpfs into a hash of Tmpfs objects
|
||||||
|
|
|
@ -67,6 +67,37 @@ options:
|
||||||
- Corresponds to the C(--placement-pref) option of C(docker service create).
|
- Corresponds to the C(--placement-pref) option of C(docker service create).
|
||||||
- Requires API version >= 1.27.
|
- Requires API version >= 1.27.
|
||||||
version_added: 2.8
|
version_added: 2.8
|
||||||
|
healthcheck:
|
||||||
|
type: dict
|
||||||
|
description:
|
||||||
|
- Configure a check that is run to determine whether or not containers for this service are "healthy".
|
||||||
|
See the docs for the L(HEALTHCHECK Dockerfile instruction,https://docs.docker.com/engine/reference/builder/#healthcheck)
|
||||||
|
for details on how healthchecks work.
|
||||||
|
- "I(interval), I(timeout) and I(start_period) are specified as durations. They accept duration as a string in a format
|
||||||
|
that look like: C(5h34m56s), C(1m30s) etc. The supported units are C(us), C(ms), C(s), C(m) and C(h)."
|
||||||
|
- Requires API version >= 1.25.
|
||||||
|
suboptions:
|
||||||
|
test:
|
||||||
|
description:
|
||||||
|
- Command to run to check health.
|
||||||
|
- Must be either a string or a list. If it is a list, the first item must be one of C(NONE), C(CMD) or C(CMD-SHELL).
|
||||||
|
interval:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- Time between running the check.
|
||||||
|
timeout:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- Maximum time to allow one check to run.
|
||||||
|
retries:
|
||||||
|
type: int
|
||||||
|
description:
|
||||||
|
- Consecutive failures needed to report unhealthy. It accept integer value.
|
||||||
|
start_period:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- Start period for the container to initialize before starting health-retries countdown.
|
||||||
|
version_added: "2.8"
|
||||||
hostname:
|
hostname:
|
||||||
type: str
|
type: str
|
||||||
description:
|
description:
|
||||||
|
@ -567,10 +598,23 @@ EXAMPLES = '''
|
||||||
docker_swarm_service:
|
docker_swarm_service:
|
||||||
name: myservice
|
name: myservice
|
||||||
state: absent
|
state: absent
|
||||||
|
|
||||||
|
- name: Start service with healthcheck
|
||||||
|
docker_swarm_service:
|
||||||
|
name: myservice
|
||||||
|
image: nginx:1.13
|
||||||
|
healthcheck:
|
||||||
|
# Check if nginx server is healthy by curl'ing the server.
|
||||||
|
# If this fails or timeouts, the healthcheck fails.
|
||||||
|
test: ["CMD", "curl", "--fail", "http://nginx.host.com"]
|
||||||
|
interval: 1m30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import time
|
|
||||||
import shlex
|
import shlex
|
||||||
|
import time
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
@ -579,6 +623,9 @@ from ansible.module_utils.docker.common import (
|
||||||
AnsibleDockerClient,
|
AnsibleDockerClient,
|
||||||
DifferenceTracker,
|
DifferenceTracker,
|
||||||
DockerBaseClass,
|
DockerBaseClass,
|
||||||
|
convert_duration_to_nanosecond,
|
||||||
|
parse_healthcheck
|
||||||
|
|
||||||
)
|
)
|
||||||
from ansible.module_utils.basic import human_to_bytes
|
from ansible.module_utils.basic import human_to_bytes
|
||||||
from ansible.module_utils.six import string_types
|
from ansible.module_utils.six import string_types
|
||||||
|
@ -649,6 +696,8 @@ class DockerService(DockerBaseClass):
|
||||||
self.args = None
|
self.args = None
|
||||||
self.endpoint_mode = None
|
self.endpoint_mode = None
|
||||||
self.dns = None
|
self.dns = None
|
||||||
|
self.healthcheck = None
|
||||||
|
self.healthcheck_disabled = None
|
||||||
self.hostname = None
|
self.hostname = None
|
||||||
self.tty = None
|
self.tty = None
|
||||||
self.dns_search = None
|
self.dns_search = None
|
||||||
|
@ -699,6 +748,8 @@ class DockerService(DockerBaseClass):
|
||||||
'dns': self.dns,
|
'dns': self.dns,
|
||||||
'dns_search': self.dns_search,
|
'dns_search': self.dns_search,
|
||||||
'dns_options': self.dns_options,
|
'dns_options': self.dns_options,
|
||||||
|
'healthcheck': self.healthcheck,
|
||||||
|
'healthcheck_disabled': self.healthcheck_disabled,
|
||||||
'hostname': self.hostname,
|
'hostname': self.hostname,
|
||||||
'env': self.env,
|
'env': self.env,
|
||||||
'force_update': self.force_update,
|
'force_update': self.force_update,
|
||||||
|
@ -740,6 +791,7 @@ class DockerService(DockerBaseClass):
|
||||||
s.dns = ap['dns']
|
s.dns = ap['dns']
|
||||||
s.dns_search = ap['dns_search']
|
s.dns_search = ap['dns_search']
|
||||||
s.dns_options = ap['dns_options']
|
s.dns_options = ap['dns_options']
|
||||||
|
s.healthcheck, s.healthcheck_disabled = parse_healthcheck(ap['healthcheck'])
|
||||||
s.hostname = ap['hostname']
|
s.hostname = ap['hostname']
|
||||||
s.tty = ap['tty']
|
s.tty = ap['tty']
|
||||||
s.log_driver = ap['log_driver']
|
s.log_driver = ap['log_driver']
|
||||||
|
@ -938,6 +990,8 @@ class DockerService(DockerBaseClass):
|
||||||
differences.add('dns_search', parameter=self.dns_search, active=os.dns_search)
|
differences.add('dns_search', parameter=self.dns_search, active=os.dns_search)
|
||||||
if self.dns_options is not None and self.dns_options != (os.dns_options or []):
|
if self.dns_options is not None and self.dns_options != (os.dns_options or []):
|
||||||
differences.add('dns_options', parameter=self.dns_options, active=os.dns_options)
|
differences.add('dns_options', parameter=self.dns_options, active=os.dns_options)
|
||||||
|
if self.has_healthcheck_changed(os):
|
||||||
|
differences.add('healthcheck', parameter=self.healthcheck, active=os.healthcheck)
|
||||||
if self.hostname is not None and self.hostname != os.hostname:
|
if self.hostname is not None and self.hostname != os.hostname:
|
||||||
differences.add('hostname', parameter=self.hostname, active=os.hostname)
|
differences.add('hostname', parameter=self.hostname, active=os.hostname)
|
||||||
if self.tty is not None and self.tty != os.tty:
|
if self.tty is not None and self.tty != os.tty:
|
||||||
|
@ -946,6 +1000,13 @@ class DockerService(DockerBaseClass):
|
||||||
force_update = True
|
force_update = True
|
||||||
return not differences.empty or force_update, differences, needs_rebuild, force_update
|
return not differences.empty or force_update, differences, needs_rebuild, force_update
|
||||||
|
|
||||||
|
def has_healthcheck_changed(self, old_publish):
|
||||||
|
if self.healthcheck_disabled is False and self.healthcheck is None:
|
||||||
|
return False
|
||||||
|
if self.healthcheck_disabled and old_publish.healthcheck is None:
|
||||||
|
return False
|
||||||
|
return self.healthcheck != old_publish.healthcheck
|
||||||
|
|
||||||
def has_publish_changed(self, old_publish):
|
def has_publish_changed(self, old_publish):
|
||||||
if self.publish is None:
|
if self.publish is None:
|
||||||
return False
|
return False
|
||||||
|
@ -1051,6 +1112,8 @@ class DockerService(DockerBaseClass):
|
||||||
container_spec_args['user'] = self.user
|
container_spec_args['user'] = self.user
|
||||||
if self.container_labels is not None:
|
if self.container_labels is not None:
|
||||||
container_spec_args['labels'] = self.container_labels
|
container_spec_args['labels'] = self.container_labels
|
||||||
|
if self.healthcheck is not None:
|
||||||
|
container_spec_args['healthcheck'] = types.Healthcheck(**self.healthcheck)
|
||||||
if self.hostname is not None:
|
if self.hostname is not None:
|
||||||
container_spec_args['hostname'] = self.hostname
|
container_spec_args['hostname'] = self.hostname
|
||||||
if self.stop_signal is not None:
|
if self.stop_signal is not None:
|
||||||
|
@ -1247,6 +1310,15 @@ class DockerServiceManager(object):
|
||||||
ds.args = task_template_data['ContainerSpec'].get('Args')
|
ds.args = task_template_data['ContainerSpec'].get('Args')
|
||||||
ds.stop_signal = task_template_data['ContainerSpec'].get('StopSignal')
|
ds.stop_signal = task_template_data['ContainerSpec'].get('StopSignal')
|
||||||
|
|
||||||
|
healthcheck_data = task_template_data['ContainerSpec'].get('Healthcheck')
|
||||||
|
if healthcheck_data:
|
||||||
|
options = ['test', 'interval', 'timeout', 'start_period', 'retries']
|
||||||
|
healthcheck = dict(
|
||||||
|
(key.lower(), value) for key, value in healthcheck_data.items()
|
||||||
|
if value is not None and key.lower() in options
|
||||||
|
)
|
||||||
|
ds.healthcheck = healthcheck
|
||||||
|
|
||||||
update_config_data = raw_data['Spec'].get('UpdateConfig')
|
update_config_data = raw_data['Spec'].get('UpdateConfig')
|
||||||
if update_config_data:
|
if update_config_data:
|
||||||
ds.update_delay = update_config_data.get('Delay')
|
ds.update_delay = update_config_data.get('Delay')
|
||||||
|
@ -1527,6 +1599,10 @@ def _detect_publish_mode_usage(client):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _detect_healthcheck_start_period(client):
|
||||||
|
return client.module.params['healthcheck']['start_period'] is not None
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
name=dict(required=True),
|
name=dict(required=True),
|
||||||
|
@ -1578,6 +1654,13 @@ def main():
|
||||||
dns=dict(type='list'),
|
dns=dict(type='list'),
|
||||||
dns_search=dict(type='list'),
|
dns_search=dict(type='list'),
|
||||||
dns_options=dict(type='list'),
|
dns_options=dict(type='list'),
|
||||||
|
healthcheck=dict(type='dict', options=dict(
|
||||||
|
test=dict(type='raw'),
|
||||||
|
interval=dict(type='str'),
|
||||||
|
timeout=dict(type='str'),
|
||||||
|
start_period=dict(type='str'),
|
||||||
|
retries=dict(type='int'),
|
||||||
|
)),
|
||||||
hostname=dict(type='str'),
|
hostname=dict(type='str'),
|
||||||
labels=dict(type='dict'),
|
labels=dict(type='dict'),
|
||||||
container_labels=dict(type='dict'),
|
container_labels=dict(type='dict'),
|
||||||
|
@ -1609,6 +1692,7 @@ def main():
|
||||||
dns_search=dict(docker_py_version='2.6.0', docker_api_version='1.25'),
|
dns_search=dict(docker_py_version='2.6.0', docker_api_version='1.25'),
|
||||||
endpoint_mode=dict(docker_py_version='3.0.0', docker_api_version='1.25'),
|
endpoint_mode=dict(docker_py_version='3.0.0', docker_api_version='1.25'),
|
||||||
force_update=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
|
force_update=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
|
||||||
|
healthcheck=dict(docker_py_version='2.0.0', docker_api_version='1.25'),
|
||||||
hostname=dict(docker_py_version='2.2.0', docker_api_version='1.25'),
|
hostname=dict(docker_py_version='2.2.0', docker_api_version='1.25'),
|
||||||
tty=dict(docker_py_version='2.4.0', docker_api_version='1.25'),
|
tty=dict(docker_py_version='2.4.0', docker_api_version='1.25'),
|
||||||
secrets=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
|
secrets=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
|
||||||
|
@ -1625,6 +1709,12 @@ def main():
|
||||||
docker_api_version='1.25',
|
docker_api_version='1.25',
|
||||||
detect_usage=_detect_publish_mode_usage,
|
detect_usage=_detect_publish_mode_usage,
|
||||||
usage_msg='set publish.mode'
|
usage_msg='set publish.mode'
|
||||||
|
),
|
||||||
|
healthcheck_start_period=dict(
|
||||||
|
docker_py_version='2.4.0',
|
||||||
|
docker_api_version='1.25',
|
||||||
|
detect_usage=_detect_healthcheck_start_period,
|
||||||
|
usage_msg='set healthcheck.start_period'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -889,6 +889,133 @@
|
||||||
- "('version is ' ~ docker_api_version ~'. Minimum version required is 1.25') in force_update_1.msg"
|
- "('version is ' ~ docker_api_version ~'. Minimum version required is 1.25') in force_update_1.msg"
|
||||||
when: docker_api_version is version('1.25', '<')
|
when: docker_api_version is version('1.25', '<')
|
||||||
|
|
||||||
|
####################################################################
|
||||||
|
## healthcheck #####################################################
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- name: healthcheck
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- sleep
|
||||||
|
- 1
|
||||||
|
timeout: 2s
|
||||||
|
interval: 0h0m2s3ms4us
|
||||||
|
retries: 2
|
||||||
|
register: healthcheck_1
|
||||||
|
|
||||||
|
- name: healthcheck (idempotency)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- sleep
|
||||||
|
- 1
|
||||||
|
timeout: 2s
|
||||||
|
interval: 0h0m2s3ms4us
|
||||||
|
retries: 2
|
||||||
|
register: healthcheck_2
|
||||||
|
|
||||||
|
- name: healthcheck (changed)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- sleep
|
||||||
|
- 1
|
||||||
|
timeout: 3s
|
||||||
|
interval: 0h1m2s3ms4us
|
||||||
|
retries: 3
|
||||||
|
register: healthcheck_3
|
||||||
|
|
||||||
|
- name: healthcheck (disabled)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- NONE
|
||||||
|
register: healthcheck_4
|
||||||
|
|
||||||
|
- name: healthcheck (disabled, idempotency)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- NONE
|
||||||
|
register: healthcheck_5
|
||||||
|
|
||||||
|
- name: healthcheck (string in healthcheck test, changed)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test: "sleep 1"
|
||||||
|
register: healthcheck_6
|
||||||
|
|
||||||
|
- name: healthcheck (string in healthcheck test, idempotency)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
healthcheck:
|
||||||
|
test: "sleep 1"
|
||||||
|
register: healthcheck_7
|
||||||
|
|
||||||
|
- name: healthcheck (empty)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
labels: {}
|
||||||
|
register: healthcheck_8
|
||||||
|
|
||||||
|
- name: healthcheck (empty idempotency)
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
image: alpine:3.8
|
||||||
|
command: '/bin/sh -v -c "sleep 10m"'
|
||||||
|
labels: {}
|
||||||
|
register: healthcheck_9
|
||||||
|
|
||||||
|
- name: cleanup
|
||||||
|
docker_swarm_service:
|
||||||
|
name: "{{ service_name }}"
|
||||||
|
state: absent
|
||||||
|
diff: no
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- healthcheck_1 is changed
|
||||||
|
- healthcheck_2 is not changed
|
||||||
|
- healthcheck_3 is changed
|
||||||
|
- healthcheck_4 is changed
|
||||||
|
- healthcheck_5 is not changed
|
||||||
|
- healthcheck_6 is changed
|
||||||
|
- healthcheck_7 is not changed
|
||||||
|
- healthcheck_8 is changed
|
||||||
|
- healthcheck_9 is not changed
|
||||||
|
when: docker_py_version is version('2.4.0', '>=')
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- healthcheck_1 is failed
|
||||||
|
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.4.0') in healthcheck_1.msg"
|
||||||
|
when: docker_py_version is version('2.4.0', '<')
|
||||||
|
|
||||||
###################################################################
|
###################################################################
|
||||||
## hostname #######################################################
|
## hostname #######################################################
|
||||||
###################################################################
|
###################################################################
|
||||||
|
|
|
@ -12,6 +12,8 @@ service_expected_output:
|
||||||
endpoint_mode: vip
|
endpoint_mode: vip
|
||||||
env: null
|
env: null
|
||||||
force_update: null
|
force_update: null
|
||||||
|
healthcheck: null
|
||||||
|
healthcheck_disabled: null
|
||||||
hostname: null
|
hostname: null
|
||||||
image: busybox
|
image: busybox
|
||||||
labels: null
|
labels: null
|
||||||
|
|
|
@ -3,6 +3,8 @@ import pytest
|
||||||
from ansible.module_utils.docker.common import (
|
from ansible.module_utils.docker.common import (
|
||||||
compare_dict_allow_more_present,
|
compare_dict_allow_more_present,
|
||||||
compare_generic,
|
compare_generic,
|
||||||
|
convert_duration_to_nanosecond,
|
||||||
|
parse_healthcheck
|
||||||
)
|
)
|
||||||
|
|
||||||
DICT_ALLOW_MORE_PRESENT = (
|
DICT_ALLOW_MORE_PRESENT = (
|
||||||
|
@ -462,3 +464,52 @@ def test_dict_allow_more_present(entry):
|
||||||
@pytest.mark.parametrize("entry", COMPARE_GENERIC)
|
@pytest.mark.parametrize("entry", COMPARE_GENERIC)
|
||||||
def test_compare_generic(entry):
|
def test_compare_generic(entry):
|
||||||
assert compare_generic(entry['a'], entry['b'], entry['method'], entry['type']) == entry['result']
|
assert compare_generic(entry['a'], entry['b'], entry['method'], entry['type']) == entry['result']
|
||||||
|
|
||||||
|
|
||||||
|
def test_convert_duration_to_nanosecond():
|
||||||
|
nanoseconds = convert_duration_to_nanosecond('5s')
|
||||||
|
assert nanoseconds == 5000000000
|
||||||
|
nanoseconds = convert_duration_to_nanosecond('1m5s')
|
||||||
|
assert nanoseconds == 65000000000
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
convert_duration_to_nanosecond([1, 2, 3])
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
convert_duration_to_nanosecond('10x')
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_healthcheck():
|
||||||
|
result, disabled = parse_healthcheck({
|
||||||
|
'test': 'sleep 1',
|
||||||
|
'interval': '1s',
|
||||||
|
})
|
||||||
|
assert disabled is False
|
||||||
|
assert result == {
|
||||||
|
'test': ['CMD-SHELL', 'sleep 1'],
|
||||||
|
'interval': 1000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
result, disabled = parse_healthcheck({
|
||||||
|
'test': ['NONE'],
|
||||||
|
})
|
||||||
|
assert result is None
|
||||||
|
assert disabled
|
||||||
|
|
||||||
|
result, disabled = parse_healthcheck({
|
||||||
|
'test': 'sleep 1',
|
||||||
|
'interval': '1s423ms'
|
||||||
|
})
|
||||||
|
assert result == {
|
||||||
|
'test': ['CMD-SHELL', 'sleep 1'],
|
||||||
|
'interval': 1423000000
|
||||||
|
}
|
||||||
|
assert disabled is False
|
||||||
|
|
||||||
|
result, disabled = parse_healthcheck({
|
||||||
|
'test': 'sleep 1',
|
||||||
|
'interval': '1h1m2s3ms4us'
|
||||||
|
})
|
||||||
|
assert result == {
|
||||||
|
'test': ['CMD-SHELL', 'sleep 1'],
|
||||||
|
'interval': 3662003004000
|
||||||
|
}
|
||||||
|
assert disabled is False
|
||||||
|
|
Loading…
Reference in a new issue