docker_swarm_service: Compare image by digest (#51134)
* Compare image by digest * Add changelog fragment * Fix version check * Remove unused import * Add note about image resolving * Don’t overwrite image * Fix documentation error * Add resolve_image option * Add version_added * Remove whitespace * Remove unused attribute * Remove unused attribute
This commit is contained in:
parent
6846152c46
commit
72a44e144a
4 changed files with 100 additions and 7 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "docker_swarm_service - Resolve image digest from registry to detect and deploy changed images. This behaviour can be turned of by using the new option ``resolve_image: false``"
|
|
@ -23,10 +23,18 @@ options:
|
|||
description:
|
||||
- Service name
|
||||
image:
|
||||
type: str
|
||||
required: true
|
||||
description:
|
||||
- Service image path and tag.
|
||||
Maps docker service IMAGE parameter.
|
||||
resolve_image:
|
||||
type: bool
|
||||
required: false
|
||||
default: true
|
||||
description:
|
||||
- If the current image digest should be resolved from registry and updated if changed.
|
||||
version_added: 2.8
|
||||
state:
|
||||
required: true
|
||||
default: present
|
||||
|
@ -335,6 +343,9 @@ requirements:
|
|||
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
|
||||
Version 2.1.0 or newer is only available with the C(docker) module."
|
||||
- "Docker API >= 1.24"
|
||||
notes:
|
||||
- "Images will only resolve to the latest digest when using Docker API >= 1.30 and docker-py >= 3.2.0.
|
||||
When using older versions use C(force_update: true) to trigger the swarm to resolve a new image."
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
|
@ -519,9 +530,9 @@ import time
|
|||
import shlex
|
||||
import operator
|
||||
from ansible.module_utils.docker_common import (
|
||||
DockerBaseClass,
|
||||
AnsibleDockerClient,
|
||||
DifferenceTracker,
|
||||
DockerBaseClass,
|
||||
)
|
||||
from ansible.module_utils.basic import human_to_bytes
|
||||
from ansible.module_utils.six import string_types
|
||||
|
@ -530,6 +541,8 @@ from ansible.module_utils._text import to_text
|
|||
try:
|
||||
from distutils.version import LooseVersion
|
||||
from docker import types
|
||||
from docker.utils import parse_repository_tag
|
||||
from docker.errors import DockerException
|
||||
except Exception:
|
||||
# missing docker-py handled in ansible.module_utils.docker
|
||||
pass
|
||||
|
@ -622,11 +635,11 @@ class DockerService(DockerBaseClass):
|
|||
'update_order': self.update_order}
|
||||
|
||||
@staticmethod
|
||||
def from_ansible_params(ap, old_service):
|
||||
def from_ansible_params(ap, old_service, image_digest):
|
||||
s = DockerService()
|
||||
s.image = image_digest
|
||||
s.constraints = ap['constraints']
|
||||
s.placement_preferences = ap['placement_preferences']
|
||||
s.image = ap['image']
|
||||
s.args = ap['args']
|
||||
s.endpoint_mode = ap['endpoint_mode']
|
||||
s.dns = ap['dns']
|
||||
|
@ -818,8 +831,9 @@ class DockerService(DockerBaseClass):
|
|||
differences.add('update_max_failure_ratio', parameter=self.update_max_failure_ratio, active=os.update_max_failure_ratio)
|
||||
if self.update_order is not None and self.update_order != os.update_order:
|
||||
differences.add('update_order', parameter=self.update_order, active=os.update_order)
|
||||
if self.image != os.image.split('@')[0]:
|
||||
differences.add('image', parameter=self.image, active=os.image.split('@')[0])
|
||||
has_image_changed, change = self.has_image_changed(os.image)
|
||||
if has_image_changed:
|
||||
differences.add('image', parameter=self.image, active=change)
|
||||
if self.user and self.user != os.user:
|
||||
differences.add('user', parameter=self.user, active=os.user)
|
||||
if self.dns != os.dns:
|
||||
|
@ -857,6 +871,11 @@ class DockerService(DockerBaseClass):
|
|||
return True
|
||||
return False
|
||||
|
||||
def has_image_changed(self, old_image):
|
||||
if '@' not in self.image:
|
||||
old_image = old_image.split('@')[0]
|
||||
return self.image != old_image, old_image
|
||||
|
||||
def __str__(self):
|
||||
return str({
|
||||
'mode': self.mode,
|
||||
|
@ -1172,12 +1191,38 @@ class DockerServiceManager():
|
|||
def remove_service(self, name):
|
||||
self.client.remove_service(name)
|
||||
|
||||
def get_image_digest(self, name, resolve=True):
|
||||
if (
|
||||
not name
|
||||
or not resolve
|
||||
or self.client.docker_py_version < LooseVersion('3.2')
|
||||
or self.client.docker_api_version < LooseVersion('1.30')
|
||||
):
|
||||
return name
|
||||
repo, tag = parse_repository_tag(name)
|
||||
if not tag:
|
||||
tag = 'latest'
|
||||
name = repo + ':' + tag
|
||||
distribution_data = self.client.inspect_distribution(name)
|
||||
digest = distribution_data['Descriptor']['digest']
|
||||
return '%s@%s' % (name, digest)
|
||||
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.diff_tracker = DifferenceTracker()
|
||||
|
||||
def run(self):
|
||||
module = self.client.module
|
||||
|
||||
image = module.params['image']
|
||||
try:
|
||||
image_digest = self.get_image_digest(
|
||||
name=image,
|
||||
resolve=module.params['resolve_image']
|
||||
)
|
||||
except DockerException as e:
|
||||
return module.fail_json(
|
||||
msg="Error looking for an image named %s: %s" % (image, e))
|
||||
try:
|
||||
current_service = self.get_service(module.params['name'])
|
||||
except Exception as e:
|
||||
|
@ -1185,7 +1230,11 @@ class DockerServiceManager():
|
|||
msg="Error looking for service named %s: %s" %
|
||||
(module.params['name'], e))
|
||||
try:
|
||||
new_service = DockerService.from_ansible_params(module.params, current_service)
|
||||
new_service = DockerService.from_ansible_params(
|
||||
module.params,
|
||||
current_service,
|
||||
image_digest
|
||||
)
|
||||
except Exception as e:
|
||||
return module.fail_json(
|
||||
msg="Error parsing module parameters: %s" % e)
|
||||
|
@ -1291,6 +1340,7 @@ def main():
|
|||
limit_memory=dict(default=0, type='str'),
|
||||
reserve_cpu=dict(default=0, type='float'),
|
||||
reserve_memory=dict(default=0, type='str'),
|
||||
resolve_image=dict(default=True, type='bool'),
|
||||
restart_policy_delay=dict(default=0, type='int'),
|
||||
restart_policy_attempts=dict(default=0, type='int'),
|
||||
restart_policy_window=dict(default=0, type='int'),
|
||||
|
|
|
@ -86,10 +86,14 @@
|
|||
published_port: 60001
|
||||
target_port: 60001
|
||||
|
||||
- name: fake image key as it is not predictable
|
||||
set_fact:
|
||||
ansible_docker_service_output: "{{ output.ansible_docker_service|combine({'image': 'busybox'}) }}"
|
||||
|
||||
- name: assert service matches expectations
|
||||
assert:
|
||||
that:
|
||||
- output.ansible_docker_service == service_expected_output
|
||||
- ansible_docker_service_output == service_expected_output
|
||||
|
||||
- name: delete sample service
|
||||
register: output
|
||||
|
|
|
@ -1269,6 +1269,43 @@
|
|||
- reserve_memory_2 is not changed
|
||||
- reserve_memory_3 is changed
|
||||
|
||||
###################################################################
|
||||
# resolve_image ###################################################
|
||||
###################################################################
|
||||
|
||||
- name: resolve_image (false)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
resolve_image: false
|
||||
register: resolve_image_1
|
||||
|
||||
- name: resolve_image (false idempotency)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
resolve_image: false
|
||||
register: resolve_image_2
|
||||
|
||||
- name: resolve_image (change)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
resolve_image: true
|
||||
register: resolve_image_3
|
||||
|
||||
- name: cleanup
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
state: absent
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- resolve_image_1 is changed
|
||||
- resolve_image_2 is not changed
|
||||
- resolve_image_3 is changed
|
||||
|
||||
###################################################################
|
||||
# restart_policy ##################################################
|
||||
###################################################################
|
||||
|
|
Loading…
Reference in a new issue