From 0570ab49f17b3ef5a77e6d1dd718290e54b3c5aa Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 14 Dec 2024 19:54:40 +0100 Subject: [PATCH] docker_compose_v2: add ignore_build_events option; ignore build events by default (#1011) * Add ignore_build_events option. * Adjust docs and tests. * Switch default to true. * Remove unnecessary parts from tests. (cherry picked from commit 2e7b4e4605c31a7788895e42f59c97b1687ee1e5) --- .../1011-docker_compose_v2-build-changed.yml | 7 + plugins/module_utils/compose_v2.py | 26 ++- plugins/modules/docker_compose_v2.py | 16 +- .../docker_compose_v2/tasks/tests/build.yml | 181 +++--------------- 4 files changed, 71 insertions(+), 159 deletions(-) create mode 100644 changelogs/fragments/1011-docker_compose_v2-build-changed.yml diff --git a/changelogs/fragments/1011-docker_compose_v2-build-changed.yml b/changelogs/fragments/1011-docker_compose_v2-build-changed.yml new file mode 100644 index 000000000..873fd90d1 --- /dev/null +++ b/changelogs/fragments/1011-docker_compose_v2-build-changed.yml @@ -0,0 +1,7 @@ +minor_changes: + - "docker_compose_v2 - add ``ignore_build_events`` option (default value ``true``) which allows to (not) ignore build events for change detection + (https://github.com/ansible-collections/community.docker/issues/1005, https://github.com/ansible-collections/community.docker/issues/pull/1011)." +bugfixes: + - "docker_compose_v2 - when using Compose 2.31.0 or newer, revert to the old behavior that image rebuilds, for example if ``rebuild=always``, only + result in ``changed`` if a container has been restarted + (https://github.com/ansible-collections/community.docker/issues/1005, https://github.com/ansible-collections/community.docker/issues/pull/1011)." diff --git a/plugins/module_utils/compose_v2.py b/plugins/module_utils/compose_v2.py index 5e3571f1a..c25a88c3c 100644 --- a/plugins/module_utils/compose_v2.py +++ b/plugins/module_utils/compose_v2.py @@ -57,6 +57,8 @@ 'Recreated', # Extras for pull events 'Pulled', + # Extras for built events + 'Built', )) DOCKER_STATUS_WORKING = frozenset(( 'Creating', @@ -76,6 +78,10 @@ 'Pulled', 'Pulling', )) +DOCKER_STATUS_BUILD = frozenset(( + 'Built', + 'Building', +)) DOCKER_STATUS_ERROR = frozenset(( 'Error', )) @@ -542,11 +548,13 @@ def parse_events(stderr, dry_run=False, warn_function=None, nonzero_rc=False): return events -def has_changes(events, ignore_service_pull_events=False): +def has_changes(events, ignore_service_pull_events=False, ignore_build_events=False): for event in events: if event.status in DOCKER_STATUS_WORKING: if ignore_service_pull_events and event.status in DOCKER_STATUS_PULL: continue + if ignore_build_events and event.status in DOCKER_STATUS_BUILD: + continue return True if event.resource_type == ResourceType.IMAGE_LAYER and event.status in DOCKER_PULL_PROGRESS_WORKING: return True @@ -802,8 +810,20 @@ def parse_events(self, stderr, dry_run=False, nonzero_rc=False): def emit_warnings(self, events): emit_warnings(events, warn_function=self.client.warn) - def update_result(self, result, events, stdout, stderr, ignore_service_pull_events=False): - result['changed'] = result.get('changed', False) or has_changes(events, ignore_service_pull_events=ignore_service_pull_events) + def update_result( + self, + result, + events, + stdout, + stderr, + ignore_service_pull_events=False, + ignore_build_events=False, + ): + result['changed'] = result.get('changed', False) or has_changes( + events, + ignore_service_pull_events=ignore_service_pull_events, + ignore_build_events=ignore_build_events, + ) result['actions'] = result.get('actions', []) + extract_actions(events) result['stdout'] = combine_text_output(result.get('stdout'), to_native(stdout)) result['stderr'] = combine_text_output(result.get('stderr'), to_native(stderr)) diff --git a/plugins/modules/docker_compose_v2.py b/plugins/modules/docker_compose_v2.py index 5ed873bb1..999680ec7 100644 --- a/plugins/modules/docker_compose_v2.py +++ b/plugins/modules/docker_compose_v2.py @@ -82,6 +82,16 @@ - When O(state) is V(present) or V(restarted), specify whether or not to include linked services. type: bool default: true + ignore_build_events: + description: + - Ignores image building events for change detection. + - If O(state=present) and O(ignore_build_events=true) and O(build=always), a rebuild that does + not trigger a container restart no longer results in RV(ignore:changed=true). + - Note that Docker Compose 2.31.0 is the first Compose 2.x version to emit build events. + For older versions, the behavior is always as if O(ignore_build_events=true). + type: bool + default: true + version_added: 4.2.0 recreate: description: - By default containers will be recreated when their configuration differs from the service definition. @@ -433,6 +443,7 @@ def __init__(self, client): self.dependencies = parameters['dependencies'] self.pull = parameters['pull'] self.build = parameters['build'] + self.ignore_build_events = parameters['ignore_build_events'] self.recreate = parameters['recreate'] self.remove_images = parameters['remove_images'] self.remove_volumes = parameters['remove_volumes'] @@ -508,7 +519,7 @@ def cmd_up(self): rc, stdout, stderr = self.client.call_cli(*args, cwd=self.project_src) events = self.parse_events(stderr, dry_run=self.check_mode, nonzero_rc=rc != 0) self.emit_warnings(events) - self.update_result(result, events, stdout, stderr, ignore_service_pull_events=True) + self.update_result(result, events, stdout, stderr, ignore_service_pull_events=True, ignore_build_events=self.ignore_build_events) self.update_failed(result, events, args, stdout, stderr, rc) return result @@ -539,7 +550,7 @@ def cmd_stop(self): rc_1, stdout_1, stderr_1 = self.client.call_cli(*args_1, cwd=self.project_src) events_1 = self.parse_events(stderr_1, dry_run=self.check_mode, nonzero_rc=rc_1 != 0) self.emit_warnings(events_1) - self.update_result(result, events_1, stdout_1, stderr_1, ignore_service_pull_events=True) + self.update_result(result, events_1, stdout_1, stderr_1, ignore_service_pull_events=True, ignore_build_events=self.ignore_build_events) is_failed_1 = is_failed(events_1, rc_1) if not is_failed_1 and not self._are_containers_stopped(): # Make sure all containers are stopped @@ -629,6 +640,7 @@ def main(): scale=dict(type='dict'), wait=dict(type='bool', default=False), wait_timeout=dict(type='int'), + ignore_build_events=dict(type='bool', default=True), ) argspec_ex = common_compose_argspec_ex() argument_spec.update(argspec_ex.pop('argspec')) diff --git a/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml b/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml index 1ba9182ae..9e49fda4b 100644 --- a/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml +++ b/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml @@ -13,6 +13,7 @@ {{ cname }}: build: ./build image: "{{ iname }}" + pull_policy: never stop_grace_period: 1s block: @@ -30,10 +31,6 @@ - '{{ project_src }}' - '{{ project_src }}/build' -#################################################################### -## Present ######################################################### -#################################################################### - - name: Template default project file copy: dest: '{{ project_src }}/docker-compose.yml' @@ -72,169 +69,51 @@ state: present register: present_2 - - assert: - that: - - present_1_check is changed - - present_1_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_1 is changed - - present_1.containers | length == 1 - - present_1.containers[0].Name == pname ~ '-' ~ cname ~ '-1' - - present_1.images | length == 1 - - present_1.images[0].ContainerName == pname ~ '-' ~ cname ~ '-1' - - present_1.images[0].Repository == iname - - present_1.images[0].Tag == "latest" - - present_1.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_2_check is not changed - - present_2_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_2 is not changed - - present_2.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - -#################################################################### -## Absent ########################################################## -#################################################################### - - - name: Absent (check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: absent - check_mode: true - register: absent_1_check - - - name: Absent - docker_compose_v2: - project_src: '{{ project_src }}' - state: absent - register: absent_1 - - - name: Absent (idempotent check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: absent - check_mode: true - register: absent_2_check - - - name: Absent (idempotent) - docker_compose_v2: - project_src: '{{ project_src }}' - state: absent - register: absent_2 - - - assert: - that: - - absent_1_check is changed - - absent_1_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - absent_1 is changed - - absent_1.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - absent_2_check is not changed - - absent_2_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - absent_2 is not changed - - absent_2.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - -#################################################################### -## Stopping and starting ########################################### -#################################################################### - - - name: Present stopped (check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - check_mode: true - register: present_1_check - - - name: Present stopped - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - register: present_1 - - - name: Present stopped (idempotent check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - check_mode: true - register: present_2_check - - - name: Present stopped (idempotent) - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - register: present_2 - - - name: Started (check) + - name: Present (idempotent check, build=always, ignore_build_events=false) docker_compose_v2: project_src: '{{ project_src }}' state: present + build: always + ignore_build_events: false check_mode: true register: present_3_check - - name: Started + - name: Present (idempotent, build=always, ignore_build_events=false) docker_compose_v2: project_src: '{{ project_src }}' state: present + build: always + ignore_build_events: false register: present_3 - - name: Started (idempotent check) + - name: Present (idempotent check, build=always, ignore_build_events=true) docker_compose_v2: project_src: '{{ project_src }}' state: present + build: always + ignore_build_events: true check_mode: true register: present_4_check - - name: Started (idempotent) + - name: Present (idempotent, build=always, ignore_build_events=true) docker_compose_v2: project_src: '{{ project_src }}' state: present + build: always + ignore_build_events: true register: present_4 - - name: Restarted (check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: restarted - check_mode: true - register: present_5_check - - - name: Restarted - docker_compose_v2: - project_src: '{{ project_src }}' - state: restarted - register: present_5 - - - name: Stopped (check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - check_mode: true - register: present_6_check - - - name: Stopped - docker_compose_v2: - project_src: '{{ project_src }}' - state: stopped - register: present_6 - - - name: Restarted (check) - docker_compose_v2: - project_src: '{{ project_src }}' - state: restarted - check_mode: true - register: present_7_check - - - name: Restarted - docker_compose_v2: - project_src: '{{ project_src }}' - state: restarted - register: present_7 - - - name: Cleanup - docker_compose_v2: - project_src: '{{ project_src }}' - state: absent - - assert: that: - present_1_check is changed - present_1_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - present_1 is changed + - present_1.containers | length == 1 + - present_1.containers[0].Name == pname ~ '-' ~ cname ~ '-1' + - present_1.images | length == 1 + - present_1.images[0].ContainerName == pname ~ '-' ~ cname ~ '-1' + - present_1.images[0].Repository == iname + - present_1.images[0].Tag == "latest" - present_1.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - present_2_check is not changed - present_2_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 @@ -242,21 +121,15 @@ - present_2.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - present_3_check is changed - present_3_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_3 is changed + - ((present_3 is changed) if docker_compose_version is version('2.31.0', '>=') else (present_3 is not changed)) - present_3.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_4_check is not changed + - present_4_check is changed - present_4_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - present_4 is not changed - present_4.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_5_check is changed - - present_5_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_5 is changed - - present_5.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_6_check is changed - - present_6_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_6 is changed - - present_6.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_7_check is changed - - present_7_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 - - present_7 is changed - - present_7.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 + + always: + - name: Cleanup + docker_compose_v2: + project_src: '{{ project_src }}' + state: absent