Compare commits

..

No commits in common. "main" and "5.0.3" have entirely different histories.
main ... 5.0.3

10 changed files with 353 additions and 626 deletions

File diff suppressed because it is too large Load Diff

View File

@ -4,20 +4,6 @@ Docker Community Collection Release Notes
.. contents:: Topics .. contents:: Topics
v5.0.4
======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- CLI-based modules - when parsing JSON output fails, also provide standard error output. Also provide information on the command and its result in machine-readable way (https://github.com/ansible-collections/community.docker/issues/1216, https://github.com/ansible-collections/community.docker/pull/1221).
- docker_compose_v2, docker_compose_v2_pull - adjust parsing from image pull events to changes in Docker Compose 5.0.0 (https://github.com/ansible-collections/community.docker/pull/1219).
v5.0.3 v5.0.3
====== ======

View File

@ -2330,18 +2330,3 @@ releases:
- 1214-docker_container-ports.yml - 1214-docker_container-ports.yml
- 5.0.3.yml - 5.0.3.yml
release_date: '2025-11-29' release_date: '2025-11-29'
5.0.4:
changes:
bugfixes:
- CLI-based modules - when parsing JSON output fails, also provide standard
error output. Also provide information on the command and its result in
machine-readable way (https://github.com/ansible-collections/community.docker/issues/1216,
https://github.com/ansible-collections/community.docker/pull/1221).
- docker_compose_v2, docker_compose_v2_pull - adjust parsing from image pull
events to changes in Docker Compose 5.0.0 (https://github.com/ansible-collections/community.docker/pull/1219).
release_summary: Bugfix release.
fragments:
- 1219-compose-v2-pull.yml
- 1221-cli-json-errors.yml
- 5.0.4.yml
release_date: '2025-12-06'

View File

@ -7,7 +7,7 @@
namespace: community namespace: community
name: docker name: docker
version: 5.1.0 version: 5.0.3
readme: README.md readme: README.md
authors: authors:
- Ansible Docker Working Group - Ansible Docker Working Group

View File

@ -197,11 +197,7 @@ class AnsibleDockerClientBase:
data = json.loads(stdout) data = json.loads(stdout)
except Exception as exc: # pylint: disable=broad-exception-caught except Exception as exc: # pylint: disable=broad-exception-caught
self.fail( self.fail(
f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}\n\nError output:\n{to_text(stderr)}", f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}"
cmd=self._compose_cmd_str(args),
rc=rc,
stdout=stdout,
stderr=stderr,
) )
return rc, data, stderr return rc, data, stderr
@ -227,11 +223,7 @@ class AnsibleDockerClientBase:
result.append(json.loads(line)) result.append(json.loads(line))
except Exception as exc: # pylint: disable=broad-exception-caught except Exception as exc: # pylint: disable=broad-exception-caught
self.fail( self.fail(
f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}\n\nError output:\n{to_text(stderr)}", f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}"
cmd=self._compose_cmd_str(args),
rc=rc,
stdout=stdout,
stderr=stderr,
) )
return rc, result, stderr return rc, result, stderr

View File

@ -132,7 +132,7 @@ DOCKER_PULL_PROGRESS_DONE = frozenset(
"Pull complete", "Pull complete",
) )
) )
DOCKER_PULL_PROGRESS_WORKING_OLD = frozenset( DOCKER_PULL_PROGRESS_WORKING = frozenset(
( (
"Pulling fs layer", "Pulling fs layer",
"Waiting", "Waiting",
@ -141,7 +141,6 @@ DOCKER_PULL_PROGRESS_WORKING_OLD = frozenset(
"Extracting", "Extracting",
) )
) )
DOCKER_PULL_PROGRESS_WORKING = frozenset(DOCKER_PULL_PROGRESS_WORKING_OLD | {"Working"})
class ResourceType: class ResourceType:
@ -192,7 +191,7 @@ _RE_PULL_EVENT = re.compile(
) )
_DOCKER_PULL_PROGRESS_WD = sorted( _DOCKER_PULL_PROGRESS_WD = sorted(
DOCKER_PULL_PROGRESS_DONE | DOCKER_PULL_PROGRESS_WORKING_OLD DOCKER_PULL_PROGRESS_DONE | DOCKER_PULL_PROGRESS_WORKING
) )
_RE_PULL_PROGRESS = re.compile( _RE_PULL_PROGRESS = re.compile(
@ -495,17 +494,7 @@ def parse_json_events(
# {"dry-run":true,"id":"ansible-docker-test-dc713f1f-container ==> ==>","text":"naming to ansible-docker-test-dc713f1f-image"} # {"dry-run":true,"id":"ansible-docker-test-dc713f1f-container ==> ==>","text":"naming to ansible-docker-test-dc713f1f-image"}
# (The longer form happens since Docker Compose 2.39.0) # (The longer form happens since Docker Compose 2.39.0)
continue continue
if ( if isinstance(resource_id, str) and " " in resource_id:
status in ("Working", "Done")
and isinstance(line_data.get("parent_id"), str)
and line_data["parent_id"].startswith("Image ")
):
# Compose 5.0.0+:
# {"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working"}
# {"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Done","percent":100}
resource_type = ResourceType.IMAGE_LAYER
resource_id = line_data["parent_id"][len("Image ") :]
elif isinstance(resource_id, str) and " " in resource_id:
resource_type_str, resource_id = resource_id.split(" ", 1) resource_type_str, resource_id = resource_id.split(" ", 1)
try: try:
resource_type = ResourceType.from_docker_compose_event( resource_type = ResourceType.from_docker_compose_event(
@ -524,7 +513,7 @@ def parse_json_events(
status, text = text, status status, text = text, status
elif ( elif (
text in DOCKER_PULL_PROGRESS_DONE text in DOCKER_PULL_PROGRESS_DONE
or line_data.get("text") in DOCKER_PULL_PROGRESS_WORKING_OLD or line_data.get("text") in DOCKER_PULL_PROGRESS_WORKING
): ):
resource_type = ResourceType.IMAGE_LAYER resource_type = ResourceType.IMAGE_LAYER
status, text = text, status status, text = text, status

View File

@ -81,19 +81,16 @@
- ansible.builtin.assert: - ansible.builtin.assert:
that: that:
- present_1_check is failed or present_1_check is changed - present_1_check is failed or present_1_check is changed
- present_1_check is changed or 'General error:' in present_1_check.msg - present_1_check is changed or present_1_check.msg.startswith('General error:')
- present_1_check.warnings | default([]) | select('regex', ' please report this at ') | length == 0 - present_1_check.warnings | default([]) | select('regex', ' please report this at ') | length == 0
- present_1 is failed - present_1 is failed
- >- - present_1.msg.startswith('General error:')
'General error:' in present_1.msg
- present_1.warnings | default([]) | select('regex', ' please report this at ') | length == 0 - present_1.warnings | default([]) | select('regex', ' please report this at ') | length == 0
- present_2_check is failed - present_2_check is failed
- present_2_check.msg.startswith('Error when processing ' ~ cname ~ ':') or - present_2_check.msg.startswith('Error when processing ' ~ cname ~ ':')
present_2_check.msg.startswith('Error when processing image ' ~ non_existing_image ~ ':')
- present_2_check.warnings | default([]) | select('regex', ' please report this at ') | length == 0 - present_2_check.warnings | default([]) | select('regex', ' please report this at ') | length == 0
- present_2 is failed - present_2 is failed
- present_2.msg.startswith('Error when processing ' ~ cname ~ ':') or - present_2.msg.startswith('Error when processing ' ~ cname ~ ':')
present_2.msg.startswith('Error when processing image ' ~ non_existing_image ~ ':')
- present_2.warnings | default([]) | select('regex', ' please report this at ') | length == 0 - present_2.warnings | default([]) | select('regex', ' please report this at ') | length == 0
#################################################################### ####################################################################

View File

@ -9,10 +9,12 @@
non_existing_image: does-not-exist:latest non_existing_image: does-not-exist:latest
project_src: "{{ remote_tmp_dir }}/{{ pname }}" project_src: "{{ remote_tmp_dir }}/{{ pname }}"
test_service_non_existing: | test_service_non_existing: |
version: '3'
services: services:
{{ cname }}: {{ cname }}:
image: {{ non_existing_image }} image: {{ non_existing_image }}
test_service_simple: | test_service_simple: |
version: '3'
services: services:
{{ cname }}: {{ cname }}:
image: {{ docker_test_image_simple_1 }} image: {{ docker_test_image_simple_1 }}

View File

@ -77,8 +77,7 @@
- ansible.builtin.assert: - ansible.builtin.assert:
that: that:
- result_1.rc == 0 - result_1.rc == 0
# Since Compose 5, unrelated output shows up in stderr... - result_1.stderr == ""
- result_1.stderr == "" or ("Creating" in result_1.stderr and "Created" in result_1.stderr)
- >- - >-
"usr" in result_1.stdout_lines "usr" in result_1.stdout_lines
and and

View File

@ -9,7 +9,6 @@ import pytest
from ansible_collections.community.docker.plugins.module_utils._compose_v2 import ( from ansible_collections.community.docker.plugins.module_utils._compose_v2 import (
Event, Event,
parse_events, parse_events,
parse_json_events,
) )
from .compose_v2_test_cases import EVENT_TEST_CASES from .compose_v2_test_cases import EVENT_TEST_CASES
@ -385,208 +384,3 @@ def test_parse_events(
assert collected_events == events assert collected_events == events
assert collected_warnings == warnings assert collected_warnings == warnings
JSON_TEST_CASES: list[tuple[str, str, str, list[Event], list[str]]] = [
(
"pull-compose-2",
"2.40.3",
'{"level":"warning","msg":"/tmp/ansible.f9pcm_i3.test/ansible-docker-test-3c46cd06-pull/docker-compose.yml: the attribute `version`'
' is obsolete, it will be ignored, please remove it to avoid potential confusion","time":"2025-12-06T13:16:30Z"}\n'
'{"id":"ansible-docker-test-3c46cd06-cont","text":"Pulling"}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Pulling fs layer"}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Downloading","status":"[\\u003e '
' ] 6.89kB/599.9kB","current":6890,"total":599883,"percent":1}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Download complete","percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Extracting","status":"[==\\u003e '
' ] 32.77kB/599.9kB","current":32768,"total":599883,"percent":5}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Extracting","status":"[============'
'======================================\\u003e] 599.9kB/599.9kB","current":599883,"total":599883,"percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Extracting","status":"[============'
'======================================\\u003e] 599.9kB/599.9kB","current":599883,"total":599883,"percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"ansible-docker-test-3c46cd06-cont","text":"Pull complete","percent":100}\n'
'{"id":"ansible-docker-test-3c46cd06-cont","text":"Pulled"}\n',
[
Event(
"unknown",
None,
"Warning",
"/tmp/ansible.f9pcm_i3.test/ansible-docker-test-3c46cd06-pull/docker-compose.yml: the attribute `version` is obsolete,"
" it will be ignored, please remove it to avoid potential confusion",
),
Event(
"image",
"ansible-docker-test-3c46cd06-cont",
"Pulling",
None,
),
Event(
"image-layer",
"63a26ae4e8a8",
"Pulling fs layer",
None,
),
Event(
"image-layer",
"63a26ae4e8a8",
"Downloading",
"[> ] 6.89kB/599.9kB",
),
Event(
"image-layer",
"63a26ae4e8a8",
"Download complete",
None,
),
Event(
"image-layer",
"63a26ae4e8a8",
"Extracting",
"[==> ] 32.77kB/599.9kB",
),
Event(
"image-layer",
"63a26ae4e8a8",
"Extracting",
"[==================================================>] 599.9kB/599.9kB",
),
Event(
"image-layer",
"63a26ae4e8a8",
"Extracting",
"[==================================================>] 599.9kB/599.9kB",
),
Event(
"image-layer",
"63a26ae4e8a8",
"Pull complete",
None,
),
Event(
"image",
"ansible-docker-test-3c46cd06-cont",
"Pulled",
None,
),
],
[],
),
(
"pull-compose-5",
"5.0.0",
'{"level":"warning","msg":"/tmp/ansible.1n0q46aj.test/ansible-docker-test-b2fa9191-pull/docker-compose.yml: the attribute'
' `version` is obsolete, it will be ignored, please remove it to avoid potential confusion","time":"2025-12-06T13:08:22Z"}\n'
'{"id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"Pulling"}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working"}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"[\\u003e '
' ] 6.89kB/599.9kB","current":6890,"total":599883,"percent":1}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"[=============='
'====================================\\u003e] 599.9kB/599.9kB","current":599883,"total":599883,"percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working"}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Done","percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"[==\\u003e '
' ] 32.77kB/599.9kB","current":32768,"total":599883,"percent":5}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"[=============='
'====================================\\u003e] 599.9kB/599.9kB","current":599883,"total":599883,"percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Working","text":"[=============='
'====================================\\u003e] 599.9kB/599.9kB","current":599883,"total":599883,"percent":100}\n'
'{"id":"63a26ae4e8a8","parent_id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Done","percent":100}\n'
'{"id":"Image ghcr.io/ansible-collections/simple-1:tag","status":"Done","text":"Pulled"}\n',
[
Event(
"unknown",
None,
"Warning",
"/tmp/ansible.1n0q46aj.test/ansible-docker-test-b2fa9191-pull/docker-compose.yml: the attribute `version`"
" is obsolete, it will be ignored, please remove it to avoid potential confusion",
),
Event(
"image",
"ghcr.io/ansible-collections/simple-1:tag",
"Pulling",
"Working",
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
None,
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
"[> ] 6.89kB/599.9kB",
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
"[==================================================>] 599.9kB/599.9kB",
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
None,
),
Event(
"image-layer", "ghcr.io/ansible-collections/simple-1:tag", "Done", None
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
"[==> ] 32.77kB/599.9kB",
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
"[==================================================>] 599.9kB/599.9kB",
),
Event(
"image-layer",
"ghcr.io/ansible-collections/simple-1:tag",
"Working",
"[==================================================>] 599.9kB/599.9kB",
),
Event(
"image-layer", "ghcr.io/ansible-collections/simple-1:tag", "Done", None
),
Event(
"image", "ghcr.io/ansible-collections/simple-1:tag", "Pulled", "Done"
),
],
[],
),
]
@pytest.mark.parametrize(
"test_id, compose_version, stderr, events, warnings",
JSON_TEST_CASES,
ids=[tc[0] for tc in JSON_TEST_CASES],
)
def test_parse_json_events(
test_id: str,
compose_version: str,
stderr: str,
events: list[Event],
warnings: list[str],
) -> None:
collected_warnings = []
def collect_warning(msg: str) -> None:
collected_warnings.append(msg)
collected_events = parse_json_events(
stderr.encode("utf-8"),
warn_function=collect_warning,
)
print(collected_events)
print(collected_warnings)
assert collected_events == events
assert collected_warnings == warnings