From 8605a4ab52ea9efafca7f538304d5fbe541f96ba Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 13 Oct 2024 21:33:30 +0200 Subject: [PATCH] Add explicit handling of dry-run image build JSON events. These produce some one-off ID values that don't make any sense as ID values. --- changelogs/fragments/976-compose-v2.yml | 2 + plugins/module_utils/compose_v2.py | 14 + .../targets/docker_compose_v2/tasks/main.yml | 11 +- .../docker_compose_v2/tasks/tests/build.yml | 262 ++++++++++++++++++ 4 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/976-compose-v2.yml create mode 100644 tests/integration/targets/docker_compose_v2/tasks/tests/build.yml diff --git a/changelogs/fragments/976-compose-v2.yml b/changelogs/fragments/976-compose-v2.yml new file mode 100644 index 000000000..a63915519 --- /dev/null +++ b/changelogs/fragments/976-compose-v2.yml @@ -0,0 +1,2 @@ +bugfixes: + - "docker_compose_v2 - improve parsing of dry-run image build operations from JSON events (https://github.com/ansible-collections/community.docker/issues/975, https://github.com/ansible-collections/community.docker/pull/976)." diff --git a/plugins/module_utils/compose_v2.py b/plugins/module_utils/compose_v2.py index b96f921ee..5e3571f1a 100644 --- a/plugins/module_utils/compose_v2.py +++ b/plugins/module_utils/compose_v2.py @@ -119,6 +119,7 @@ def from_docker_compose_event(cls, resource_type): "Image": cls.IMAGE, "Volume": cls.VOLUME, "Container": cls.CONTAINER, + "Service": cls.SERVICE, }[resource_type] @@ -420,6 +421,19 @@ def parse_json_events(stderr, warn_function=None): resource_id = line_data.get('id') status = line_data.get('status') text = line_data.get('text') + if resource_id == " " and text and text.startswith("build service "): + # Example: + # {"dry-run":true,"id":" ","text":"build service app"} + resource_id = "S" + text[len("build s"):] + text = "Building" + if resource_id == "==>" and text and text.startswith("==> writing image "): + # Example: + # {"dry-run":true,"id":"==>","text":"==> writing image dryRun-7d1043473d55bfa90e8530d35801d4e381bc69f0"} + continue + if resource_id == "==> ==>" and text and text.startswith("naming to "): + # Example: + # {"dry-run":true,"id":"==> ==>","text":"naming to display-app"} + continue if isinstance(resource_id, str) and ' ' in resource_id: resource_type_str, resource_id = resource_id.split(' ', 1) try: diff --git a/tests/integration/targets/docker_compose_v2/tasks/main.yml b/tests/integration/targets/docker_compose_v2/tasks/main.yml index dbb2ece71..03fac58b2 100644 --- a/tests/integration/targets/docker_compose_v2/tasks/main.yml +++ b/tests/integration/targets/docker_compose_v2/tasks/main.yml @@ -14,6 +14,7 @@ name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}" cnames: [] dnetworks: [] + images: [] - debug: msg: "Using name prefix {{ name_prefix }}" @@ -35,7 +36,7 @@ name: "{{ item }}" state: absent force_kill: true - with_items: "{{ cnames }}" + loop: "{{ cnames }}" diff: false - name: "Make sure all networks are removed" @@ -43,7 +44,13 @@ name: "{{ item }}" state: absent force: true - with_items: "{{ dnetworks }}" + loop: "{{ dnetworks }}" + diff: false + + - name: "Make sure all images are removed" + docker_image_remove: + name: "{{ item }}" + loop: "{{ images }}" diff: false when: docker_has_compose and docker_compose_version is version('2.18.0', '>=') diff --git a/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml b/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml new file mode 100644 index 000000000..1ba9182ae --- /dev/null +++ b/tests/integration/targets/docker_compose_v2/tasks/tests/build.yml @@ -0,0 +1,262 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- vars: + pname: "{{ name_prefix }}-build" + cname: "{{ name_prefix }}-container" + iname: "{{ name_prefix }}-image" + project_src: "{{ remote_tmp_dir }}/{{ pname }}" + test_service: | + services: + {{ cname }}: + build: ./build + image: "{{ iname }}" + stop_grace_period: 1s + + block: + - name: Registering container name + set_fact: + cnames: "{{ cnames + [pname ~ '-' ~ cname ~ '-1'] }}" + dnetworks: "{{ dnetworks + [pname ~ '_default'] }}" + images: "{{ images + [iname] }}" + + - name: Create project directory + file: + path: '{{ item }}' + state: directory + loop: + - '{{ project_src }}' + - '{{ project_src }}/build' + +#################################################################### +## Present ######################################################### +#################################################################### + + - name: Template default project file + copy: + dest: '{{ project_src }}/docker-compose.yml' + content: '{{ test_service }}' + + - name: Template Dockerfile + copy: + dest: '{{ project_src }}/build/Dockerfile' + content: | + FROM {{ docker_test_image_alpine }} + ENTRYPOINT ["/bin/sh", "-c", "sleep 10m"] + + - name: Present (check) + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + check_mode: true + register: present_1_check + + - name: Present + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + register: present_1 + + - name: Present (idempotent check) + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + check_mode: true + register: present_2_check + + - name: Present (idempotent) + docker_compose_v2: + project_src: '{{ project_src }}' + 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) + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + check_mode: true + register: present_3_check + + - name: Started + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + register: present_3 + + - name: Started (idempotent check) + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + check_mode: true + register: present_4_check + + - name: Started (idempotent) + docker_compose_v2: + project_src: '{{ project_src }}' + state: present + 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.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 + - present_3_check is changed + - present_3_check.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 + - present_3 is changed + - present_3.warnings | default([]) | select('regex', ' Please report this at ') | length == 0 + - present_4_check is not 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