mirror of
https://github.com/ansible-collections/community.docker.git
synced 2025-12-13 02:22:04 +00:00
Compare commits
14 Commits
ca38a543e8
...
3a79a9ef96
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a79a9ef96 | ||
|
|
947ec9a442 | ||
|
|
25e7ba222e | ||
|
|
6ab8cc0d82 | ||
|
|
159df0ab91 | ||
|
|
174c0c8058 | ||
|
|
2efcd6b2ec | ||
|
|
260e9cc254 | ||
|
|
76b5c2c742 | ||
|
|
ddbbbe5b9e | ||
|
|
f9925d770e | ||
|
|
c8ff5847a3 | ||
|
|
48745bf8c1 | ||
|
|
30f3a1321c |
717
CHANGELOG.md
717
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,20 @@ Docker Community Collection Release Notes
|
||||
|
||||
.. 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
|
||||
======
|
||||
|
||||
|
||||
@ -99,6 +99,7 @@ If you use the Ansible package and do not update collections independently, use
|
||||
- community.docker.docker_volume_info: retrieve information on Docker volumes
|
||||
* Docker Compose:
|
||||
- community.docker.docker_compose_v2: manage Docker Compose files (Docker compose CLI plugin)
|
||||
- community.docker.docker_compose_v2_build: build images for a Docker compose project
|
||||
- community.docker.docker_compose_v2_exec: run command in a container of a Compose service
|
||||
- community.docker.docker_compose_v2_pull: pull a Docker compose project
|
||||
- community.docker.docker_compose_v2_run: run command in a new container of a Compose service
|
||||
|
||||
@ -2330,3 +2330,18 @@ releases:
|
||||
- 1214-docker_container-ports.yml
|
||||
- 5.0.3.yml
|
||||
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'
|
||||
|
||||
@ -263,6 +263,9 @@ There are several modules for working with Docker Compose projects:
|
||||
community.docker.docker_compose_v2
|
||||
The :ansplugin:`community.docker.docker_compose_v2 module <community.docker.docker_compose_v2#module>` allows you to use your existing Docker Compose files to orchestrate containers on a single Docker daemon or on Swarm.
|
||||
|
||||
community.docker.docker_compose_v2_build
|
||||
The :ansplugin:`community.docker.docker_compose_v2_pull module <community.docker.docker_compose_v2_pull#module>` allows you to build images for Docker compose projects.
|
||||
|
||||
community.docker.docker_compose_v2_exec
|
||||
The :ansplugin:`community.docker.docker_compose_v2_exec module <community.docker.docker_compose_v2_exec#module>` allows you to run a command in a container of Docker Compose projects.
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ requires_ansible: '>=2.17.0'
|
||||
action_groups:
|
||||
docker:
|
||||
- docker_compose_v2
|
||||
- docker_compose_v2_build
|
||||
- docker_compose_v2_exec
|
||||
- docker_compose_v2_pull
|
||||
- docker_compose_v2_run
|
||||
|
||||
@ -197,7 +197,11 @@ class AnsibleDockerClientBase:
|
||||
data = json.loads(stdout)
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(
|
||||
f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}"
|
||||
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)}",
|
||||
cmd=self._compose_cmd_str(args),
|
||||
rc=rc,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
)
|
||||
return rc, data, stderr
|
||||
|
||||
@ -223,7 +227,11 @@ class AnsibleDockerClientBase:
|
||||
result.append(json.loads(line))
|
||||
except Exception as exc: # pylint: disable=broad-exception-caught
|
||||
self.fail(
|
||||
f"Error while parsing JSON output of {self._compose_cmd_str(args)}: {exc}\nJSON output: {to_text(stdout)}"
|
||||
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)}",
|
||||
cmd=self._compose_cmd_str(args),
|
||||
rc=rc,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
)
|
||||
return rc, result, stderr
|
||||
|
||||
|
||||
@ -132,7 +132,7 @@ DOCKER_PULL_PROGRESS_DONE = frozenset(
|
||||
"Pull complete",
|
||||
)
|
||||
)
|
||||
DOCKER_PULL_PROGRESS_WORKING = frozenset(
|
||||
DOCKER_PULL_PROGRESS_WORKING_OLD = frozenset(
|
||||
(
|
||||
"Pulling fs layer",
|
||||
"Waiting",
|
||||
@ -141,6 +141,7 @@ DOCKER_PULL_PROGRESS_WORKING = frozenset(
|
||||
"Extracting",
|
||||
)
|
||||
)
|
||||
DOCKER_PULL_PROGRESS_WORKING = frozenset(DOCKER_PULL_PROGRESS_WORKING_OLD | {"Working"})
|
||||
|
||||
|
||||
class ResourceType:
|
||||
@ -191,7 +192,7 @@ _RE_PULL_EVENT = re.compile(
|
||||
)
|
||||
|
||||
_DOCKER_PULL_PROGRESS_WD = sorted(
|
||||
DOCKER_PULL_PROGRESS_DONE | DOCKER_PULL_PROGRESS_WORKING
|
||||
DOCKER_PULL_PROGRESS_DONE | DOCKER_PULL_PROGRESS_WORKING_OLD
|
||||
)
|
||||
|
||||
_RE_PULL_PROGRESS = re.compile(
|
||||
@ -494,7 +495,17 @@ def parse_json_events(
|
||||
# {"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)
|
||||
continue
|
||||
if isinstance(resource_id, str) and " " in resource_id:
|
||||
if (
|
||||
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)
|
||||
try:
|
||||
resource_type = ResourceType.from_docker_compose_event(
|
||||
@ -513,7 +524,7 @@ def parse_json_events(
|
||||
status, text = text, status
|
||||
elif (
|
||||
text in DOCKER_PULL_PROGRESS_DONE
|
||||
or line_data.get("text") in DOCKER_PULL_PROGRESS_WORKING
|
||||
or line_data.get("text") in DOCKER_PULL_PROGRESS_WORKING_OLD
|
||||
):
|
||||
resource_type = ResourceType.IMAGE_LAYER
|
||||
status, text = text, status
|
||||
|
||||
@ -173,7 +173,10 @@ author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
|
||||
seealso:
|
||||
- module: community.docker.docker_compose_v2_build
|
||||
- module: community.docker.docker_compose_v2_exec
|
||||
- module: community.docker.docker_compose_v2_pull
|
||||
- module: community.docker.docker_compose_v2_run
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
|
||||
190
plugins/modules/docker_compose_v2_build.py
Normal file
190
plugins/modules/docker_compose_v2_build.py
Normal file
@ -0,0 +1,190 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2023, Felix Fontein <felix@fontein.de>
|
||||
# Copyright (c) 2025, Maciej Bogusz (@mjbogusz)
|
||||
# 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
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
module: docker_compose_v2_build
|
||||
|
||||
short_description: Build a Docker compose project
|
||||
|
||||
version_added: 4.7.0
|
||||
|
||||
description:
|
||||
- Uses Docker Compose to build images for a project.
|
||||
extends_documentation_fragment:
|
||||
- community.docker.compose_v2
|
||||
- community.docker.compose_v2.minimum_version
|
||||
- community.docker.docker.cli_documentation
|
||||
- community.docker.attributes
|
||||
- community.docker.attributes.actiongroup_docker
|
||||
|
||||
attributes:
|
||||
check_mode:
|
||||
support: full
|
||||
diff_mode:
|
||||
support: none
|
||||
idempotent:
|
||||
support: full
|
||||
|
||||
options:
|
||||
no_cache:
|
||||
description:
|
||||
- If set to V(true), will not use cache when building the images.
|
||||
type: bool
|
||||
default: false
|
||||
pull:
|
||||
description:
|
||||
- If set to V(true), will attempt to pull newer version of the image.
|
||||
type: bool
|
||||
default: false
|
||||
with_dependencies:
|
||||
description:
|
||||
- If set to V(true), also build services that are declared as dependencies.
|
||||
- This only makes sense if O(services) is used.
|
||||
type: bool
|
||||
default: false
|
||||
memory_limit:
|
||||
description:
|
||||
- Memory limit for the build container, in bytes. Not supported by BuildKit.
|
||||
type: int
|
||||
services:
|
||||
description:
|
||||
- Specifies a subset of services to be targeted.
|
||||
type: list
|
||||
elements: str
|
||||
|
||||
author:
|
||||
- Maciej Bogusz (@mjbogusz)
|
||||
|
||||
seealso:
|
||||
- module: community.docker.docker_compose_v2
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
- name: Build images for flask project
|
||||
community.docker.docker_compose_v2_build:
|
||||
project_src: /path/to/flask
|
||||
"""
|
||||
|
||||
RETURN = r"""
|
||||
actions:
|
||||
description:
|
||||
- A list of actions that have been applied.
|
||||
returned: success
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
what:
|
||||
description:
|
||||
- What kind of resource was changed.
|
||||
type: str
|
||||
sample: container
|
||||
choices:
|
||||
- image
|
||||
- unknown
|
||||
id:
|
||||
description:
|
||||
- The ID of the resource that was changed.
|
||||
type: str
|
||||
sample: container
|
||||
status:
|
||||
description:
|
||||
- The status change that happened.
|
||||
type: str
|
||||
sample: Building
|
||||
choices:
|
||||
- Building
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils.common_cli import (
|
||||
AnsibleModuleDockerClient,
|
||||
DockerException,
|
||||
)
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils.compose_v2 import (
|
||||
BaseComposeManager,
|
||||
common_compose_argspec_ex,
|
||||
)
|
||||
|
||||
|
||||
class BuildManager(BaseComposeManager):
|
||||
def __init__(self, client):
|
||||
super(BuildManager, self).__init__(client)
|
||||
parameters = self.client.module.params
|
||||
|
||||
self.no_cache = parameters['no_cache']
|
||||
self.pull = parameters['pull']
|
||||
self.with_dependencies = parameters['with_dependencies']
|
||||
self.memory_limit = parameters['memory_limit']
|
||||
self.services = parameters['services'] or []
|
||||
|
||||
def get_build_cmd(self, dry_run):
|
||||
args = self.get_base_args() + ['build']
|
||||
if self.no_cache:
|
||||
args.append('--no-cache')
|
||||
if self.pull:
|
||||
args.append('--pull')
|
||||
if self.with_dependencies:
|
||||
args.append('--with-dependencies')
|
||||
if self.memory_limit:
|
||||
args.extend(['--memory', str(self.memory_limit)])
|
||||
if dry_run:
|
||||
args.append('--dry-run')
|
||||
args.append('--')
|
||||
for service in self.services:
|
||||
args.append(service)
|
||||
return args
|
||||
|
||||
def run(self):
|
||||
result = dict()
|
||||
args = self.get_build_cmd(self.check_mode)
|
||||
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_build_events=False)
|
||||
self.update_failed(result, events, args, stdout, stderr, rc)
|
||||
self.cleanup_result(result)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
no_cache=dict(type='bool', default=False),
|
||||
pull=dict(type='bool', default=False),
|
||||
with_dependencies=dict(type='bool', default=False),
|
||||
memory_limit=dict(type='int'),
|
||||
services=dict(type='list', elements='str'),
|
||||
)
|
||||
argspec_ex = common_compose_argspec_ex()
|
||||
argument_spec.update(argspec_ex.pop('argspec'))
|
||||
|
||||
client = AnsibleModuleDockerClient(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True,
|
||||
needs_api_version=False,
|
||||
**argspec_ex
|
||||
)
|
||||
|
||||
try:
|
||||
manager = BuildManager(client)
|
||||
result = manager.run()
|
||||
manager.cleanup()
|
||||
client.module.exit_json(**result)
|
||||
except DockerException as e:
|
||||
client.fail('An unexpected docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -81,16 +81,19 @@
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- present_1_check is failed or present_1_check is changed
|
||||
- present_1_check is changed or present_1_check.msg.startswith('General error:')
|
||||
- present_1_check is changed or 'General error:' in present_1_check.msg
|
||||
- present_1_check.warnings | default([]) | select('regex', ' please report this at ') | length == 0
|
||||
- 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_2_check is failed
|
||||
- present_2_check.msg.startswith('Error when processing ' ~ cname ~ ':')
|
||||
- present_2_check.msg.startswith('Error when processing ' ~ cname ~ ':') or
|
||||
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 is failed
|
||||
- present_2.msg.startswith('Error when processing ' ~ cname ~ ':')
|
||||
- present_2.msg.startswith('Error when processing ' ~ cname ~ ':') or
|
||||
present_2.msg.startswith('Error when processing image ' ~ non_existing_image ~ ':')
|
||||
- present_2.warnings | default([]) | select('regex', ' please report this at ') | length == 0
|
||||
|
||||
####################################################################
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
# 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
|
||||
|
||||
azp/4
|
||||
destructive
|
||||
@ -0,0 +1,10 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
dependencies:
|
||||
- setup_docker_cli_compose
|
||||
# The Python dependencies are needed for the other modules
|
||||
- setup_docker_python_deps
|
||||
- setup_remote_tmp_dir
|
||||
@ -0,0 +1,59 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
####################################################################
|
||||
# WARNING: These are designed specifically for Ansible tests #
|
||||
# and should not be used as examples of how to write Ansible roles #
|
||||
####################################################################
|
||||
|
||||
# Create random name prefix (for services, ...)
|
||||
- name: Create random container name prefix
|
||||
set_fact:
|
||||
name_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}"
|
||||
cnames: []
|
||||
inames: []
|
||||
dnetworks: []
|
||||
|
||||
- debug:
|
||||
msg: "Using name prefix {{ name_prefix }}"
|
||||
|
||||
- name: Show images
|
||||
command: docker images --all --digests
|
||||
|
||||
# Run the tests
|
||||
- block:
|
||||
- name: Show docker compose --help output
|
||||
command: docker compose --help
|
||||
|
||||
- include_tasks: run-test.yml
|
||||
with_fileglob:
|
||||
- "tests/*.yml"
|
||||
loop_control:
|
||||
loop_var: test_name
|
||||
|
||||
always:
|
||||
- name: "Make sure all containers are removed"
|
||||
docker_container:
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
force_kill: true
|
||||
with_items: "{{ cnames }}"
|
||||
diff: false
|
||||
|
||||
- name: "Make sure all images are removed"
|
||||
docker_image_remove:
|
||||
name: "{{ item }}"
|
||||
with_items: "{{ inames }}"
|
||||
diff: false
|
||||
|
||||
- name: "Make sure all networks are removed"
|
||||
docker_network:
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
force: true
|
||||
with_items: "{{ dnetworks }}"
|
||||
diff: false
|
||||
|
||||
when: docker_has_compose and docker_compose_version is version('2.18.0', '>=')
|
||||
@ -0,0 +1,7 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
- name: "Loading tasks from {{ test_name }}"
|
||||
include_tasks: "{{ test_name }}"
|
||||
@ -0,0 +1,159 @@
|
||||
---
|
||||
# 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:
|
||||
bname: "{{ name_prefix }}-build"
|
||||
cname: "{{ name_prefix }}-cont"
|
||||
non_existing_image: does-not-exist:latest
|
||||
dockerfile_path: test-dockerfile
|
||||
base_image: "{{ docker_test_image_hello_world }}"
|
||||
image_name: "{{ name_prefix }}-image"
|
||||
project_src: "{{ remote_tmp_dir }}/{{ bname }}"
|
||||
test_service_non_existing: |
|
||||
services:
|
||||
{{ cname }}:
|
||||
image: {{ non_existing_image }}
|
||||
build:
|
||||
dockerfile: Dockerfile-does-not-exist
|
||||
test_service_simple: |
|
||||
services:
|
||||
{{ cname }}:
|
||||
image: {{ image_name }}
|
||||
build:
|
||||
dockerfile: {{ dockerfile_path }}
|
||||
command: 10m
|
||||
stop_grace_period: 1s
|
||||
test_service_simple_dockerfile: |
|
||||
FROM {{ base_image }}
|
||||
RUN [ "/hello" ]
|
||||
|
||||
block:
|
||||
- name: Registering container name
|
||||
set_fact:
|
||||
cnames: "{{ cnames + [bname ~ '-' ~ cname ~ '-1'] }}"
|
||||
inames: "{{ inames + [ base_image, image_name ] }}"
|
||||
dnetworks: "{{ dnetworks + [bname ~ '_default'] }}"
|
||||
|
||||
- name: Create project directory
|
||||
file:
|
||||
path: '{{ project_src }}'
|
||||
state: directory
|
||||
|
||||
- name: Make sure images are not around
|
||||
docker_image_remove:
|
||||
name: '{{ item }}'
|
||||
loop:
|
||||
- '{{ non_existing_image }}'
|
||||
- '{{ image_name }}'
|
||||
|
||||
- name: Prune docker build cache
|
||||
docker_prune:
|
||||
builder_cache: true
|
||||
builder_cache_all: true
|
||||
|
||||
####################################################################
|
||||
## Image with missing dockerfile ###################################
|
||||
####################################################################
|
||||
|
||||
- name: Template project file with non-existing image
|
||||
copy:
|
||||
dest: '{{ project_src }}/docker-compose.yml'
|
||||
content: '{{ test_service_non_existing }}'
|
||||
|
||||
- name: Build (check)
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
check_mode: true
|
||||
register: build_1_check
|
||||
ignore_errors: true
|
||||
|
||||
- name: Build
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
register: build_1
|
||||
ignore_errors: true
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- build_1_check is failed
|
||||
- >-
|
||||
build_1_check.msg | trim == "General error: failed to solve: failed to read dockerfile: open Dockerfile-does-not-exist: no such file or directory"
|
||||
- build_1_check.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
- build_1 is failed
|
||||
- >-
|
||||
build_1.msg | trim == "General error: failed to solve: failed to read dockerfile: open Dockerfile-does-not-exist: no such file or directory"
|
||||
- build_1.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
|
||||
####################################################################
|
||||
## Regular image ###################################################
|
||||
####################################################################
|
||||
|
||||
- name: Template project file with simple dockerfile
|
||||
copy:
|
||||
dest: '{{ project_src }}/docker-compose.yml'
|
||||
content: '{{ test_service_simple }}'
|
||||
|
||||
- name: Template dockerfile
|
||||
copy:
|
||||
dest: '{{ project_src }}/{{ dockerfile_path }}'
|
||||
content: '{{ test_service_simple_dockerfile }}'
|
||||
|
||||
- docker_image_info:
|
||||
name: "{{ image_name }}"
|
||||
register: pre_image
|
||||
|
||||
- name: Build (check)
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
check_mode: true
|
||||
register: build_1_check
|
||||
|
||||
- docker_image_info:
|
||||
name: "{{ image_name }}"
|
||||
register: build_1_check_image
|
||||
|
||||
- name: Build
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
register: build_1
|
||||
|
||||
- docker_image_info:
|
||||
name: "{{ image_name }}"
|
||||
register: build_1_image
|
||||
|
||||
- name: Build (idempotent, check)
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
check_mode: true
|
||||
register: build_2_check
|
||||
|
||||
- docker_image_info:
|
||||
name: "{{ image_name }}"
|
||||
register: build_2_check_image
|
||||
|
||||
- name: Build (idempotent)
|
||||
docker_compose_v2_build:
|
||||
project_src: '{{ project_src }}'
|
||||
register: build_2
|
||||
|
||||
- docker_image_info:
|
||||
name: "{{ image_name }}"
|
||||
register: build_2_image
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- build_1_check is changed
|
||||
- (build_1_check.actions | selectattr('status', 'eq', 'Building') | first) is truthy
|
||||
- build_1_check.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
- build_1 is changed
|
||||
- (build_1.actions | selectattr('status', 'eq', 'Building') | first) is truthy
|
||||
- build_1.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
|
||||
- build_2_check is not changed
|
||||
- build_2_check.actions | selectattr('status', 'eq', 'Building') | length == 0
|
||||
- build_2_check.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
- build_2 is not changed
|
||||
- build_2.actions | selectattr('status', 'eq', 'Building') | length == 0
|
||||
- build_2.warnings | default([]) | select('regex', 'Cannot parse event from ') | length == 0
|
||||
@ -9,12 +9,10 @@
|
||||
non_existing_image: does-not-exist:latest
|
||||
project_src: "{{ remote_tmp_dir }}/{{ pname }}"
|
||||
test_service_non_existing: |
|
||||
version: '3'
|
||||
services:
|
||||
{{ cname }}:
|
||||
image: {{ non_existing_image }}
|
||||
test_service_simple: |
|
||||
version: '3'
|
||||
services:
|
||||
{{ cname }}:
|
||||
image: {{ docker_test_image_simple_1 }}
|
||||
|
||||
@ -77,7 +77,8 @@
|
||||
- ansible.builtin.assert:
|
||||
that:
|
||||
- result_1.rc == 0
|
||||
- result_1.stderr == ""
|
||||
# Since Compose 5, unrelated output shows up in stderr...
|
||||
- result_1.stderr == "" or ("Creating" in result_1.stderr and "Created" in result_1.stderr)
|
||||
- >-
|
||||
"usr" in result_1.stdout_lines
|
||||
and
|
||||
|
||||
@ -9,6 +9,7 @@ import pytest
|
||||
from ansible_collections.community.docker.plugins.module_utils._compose_v2 import (
|
||||
Event,
|
||||
parse_events,
|
||||
parse_json_events,
|
||||
)
|
||||
|
||||
from .compose_v2_test_cases import EVENT_TEST_CASES
|
||||
@ -384,3 +385,208 @@ def test_parse_events(
|
||||
|
||||
assert collected_events == events
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user