Add docker_compose_v2_pull module (#751)

* Add docker_compose_v2_pull module.

* Improve and extend parsing of events.

* Add ignores.

* --policy is only available since Compose 2.22.0.
This commit is contained in:
Felix Fontein 2024-01-13 14:36:26 +01:00 committed by GitHub
parent 8ca5e2f810
commit 307dc4045a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 821 additions and 58 deletions

View File

@ -57,11 +57,24 @@ DOCKER_STATUS_ERROR = frozenset((
))
DOCKER_STATUS = frozenset(DOCKER_STATUS_DONE | DOCKER_STATUS_WORKING | DOCKER_STATUS_PULL | DOCKER_STATUS_ERROR)
DOCKER_PULL_PROGRESS_DONE = frozenset((
'Already exists',
'Download complete',
'Pull complete',
))
DOCKER_PULL_PROGRESS_WORKING = frozenset((
'Pulling fs layer',
'Downloading',
'Verifying Checksum',
'Extracting',
))
class ResourceType(object):
UNKNOWN = "unknown"
NETWORK = "network"
IMAGE = "image"
IMAGE_LAYER = "image-layer"
VOLUME = "volume"
CONTAINER = "container"
SERVICE = "service"
@ -108,6 +121,18 @@ _RE_PULL_EVENT = re.compile(
% '|'.join(re.escape(status) for status in DOCKER_STATUS_PULL)
)
_RE_PULL_PROGRESS = re.compile(
r'^'
r'\s*'
r'(?P<layer>\S+)'
r'\s+'
r'(?P<status>%s)'
r'\s*'
r'(?:|\s\[[^]]+\]\s+\S+\s*)'
r'$'
% '|'.join(re.escape(status) for status in sorted(DOCKER_PULL_PROGRESS_DONE | DOCKER_PULL_PROGRESS_WORKING))
)
_RE_ERROR_EVENT = re.compile(
r'^'
r'\s*'
@ -119,18 +144,79 @@ _RE_ERROR_EVENT = re.compile(
% '|'.join(re.escape(status) for status in DOCKER_STATUS_ERROR)
)
_RE_CONTINUE_EVENT = re.compile(
r'^'
r'\s*'
r'(?P<resource_id>\S+)'
r'\s+'
r'-'
r'\s*'
r'(?P<msg>\S(?:|.*\S))'
r'$'
)
def parse_events(stderr, dry_run=False, warn_function=None):
events = []
error_event = None
for line in stderr.splitlines():
line = to_native(line.strip())
if not line:
continue
if dry_run:
if line.startswith(_DRY_RUN_MARKER):
line = line[len(_DRY_RUN_MARKER):].lstrip()
elif error_event is None and warn_function:
_RE_SKIPPED_EVENT = re.compile(
r'^'
r'\s*'
r'(?P<resource_id>\S+)'
r'\s+'
r'Skipped -'
r'\s*'
r'(?P<msg>\S(?:|.*\S))'
r'$'
)
def _extract_event(line):
match = _RE_RESOURCE_EVENT.match(line)
if match is not None:
status = match.group('status')
msg = None
if status not in DOCKER_STATUS:
status, msg = msg, status
return Event(
ResourceType.from_docker_compose_event(match.group('resource_type')),
match.group('resource_id'),
status,
msg,
)
match = _RE_PULL_EVENT.match(line)
if match:
return Event(
ResourceType.SERVICE,
match.group('service'),
match.group('status'),
None,
)
match = _RE_ERROR_EVENT.match(line)
if match:
return Event(
ResourceType.UNKNOWN,
match.group('resource_id'),
match.group('status'),
None,
)
match = _RE_PULL_PROGRESS.match(line)
if match:
return Event(
ResourceType.IMAGE_LAYER,
match.group('layer'),
match.group('status'),
None,
)
match = _RE_SKIPPED_EVENT.match(line)
if match:
return Event(
ResourceType.UNKNOWN,
match.group('resource_id'),
'Skipped',
match.group('msg'),
)
return None
def _warn_missing_dry_run_prefix(line, warn_missing_dry_run_prefix, warn_function):
if warn_missing_dry_run_prefix and warn_function:
# This could be a bug, a change of docker compose's output format, ...
# Tell the user to report it to us :-)
warn_function(
@ -138,55 +224,70 @@ def parse_events(stderr, dry_run=False, warn_function=None):
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
)
match = _RE_RESOURCE_EVENT.match(line)
if match is not None:
status = match.group('status')
msg = None
if status not in DOCKER_STATUS:
status, msg = msg, status
event = Event(
ResourceType.from_docker_compose_event(match.group('resource_type')),
match.group('resource_id'),
status,
msg,
def _warn_unparsable_line(line, warn_function):
# This could be a bug, a change of docker compose's output format, ...
# Tell the user to report it to us :-)
if warn_function:
warn_function(
'Cannot parse event from line: {0!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
)
def _find_last_event_for(events, resource_id):
for index, event in enumerate(reversed(events)):
if event.resource_id == resource_id:
return len(events) - 1 - index, event
return None
def _concat_event_msg(event, append_msg):
return Event(
event.resource_type,
event.resource_id,
event.status,
'\n'.join(msg for msg in [event.msg, append_msg] if msg is not None),
)
def parse_events(stderr, dry_run=False, warn_function=None):
events = []
error_event = None
stderr_lines = stderr.splitlines()
if stderr_lines and stderr_lines[-1] == b'':
del stderr_lines[-1]
for line in stderr_lines:
line = to_native(line.strip())
if not line:
continue
warn_missing_dry_run_prefix = False
if dry_run:
if line.startswith(_DRY_RUN_MARKER):
line = line[len(_DRY_RUN_MARKER):].lstrip()
else:
warn_missing_dry_run_prefix = True
event = _extract_event(line)
if event is not None:
events.append(event)
if status in DOCKER_STATUS_ERROR:
if event.status in DOCKER_STATUS_ERROR:
error_event = event
else:
error_event = None
_warn_missing_dry_run_prefix(line, warn_missing_dry_run_prefix, warn_function)
continue
match = _RE_PULL_EVENT.match(line)
match = _RE_CONTINUE_EVENT.match(line)
if match:
events.append(
Event(
ResourceType.SERVICE,
match.group('service'),
match.group('status'),
None,
)
)
error_event = None
continue
match = _RE_ERROR_EVENT.match(line)
if match:
error_event = Event(
ResourceType.UNKNOWN,
match.group('resource_id'),
match.group('status'),
None,
)
events.append(error_event)
continue
# Continuing an existing event
index_event = _find_last_event_for(events, match.group('resource_id'))
if index_event is not None:
index, event = index_event
events[-1] = _concat_event_msg(event, match.group('msg'))
if error_event is not None:
# Unparsable line that apparently belongs to the previous error event
error_event = Event(
error_event.resource_type,
error_event.resource_id,
error_event.status,
'\n'.join(msg for msg in [error_event.msg, line] if msg is not None),
)
events[-1] = error_event
events[-1] = _concat_event_msg(error_event, line)
continue
if line.startswith('Error '):
# Error message that is independent of an error event
@ -198,14 +299,18 @@ def parse_events(stderr, dry_run=False, warn_function=None):
)
events.append(error_event)
continue
# This could be a bug, a change of docker compose's output format, ...
# Tell the user to report it to us :-)
if warn_function:
warn_function(
'Cannot parse event from line: {0!r}. Please report this at '
'https://github.com/ansible-collections/community.docker/issues/new?assignees=&labels=&projects=&template=bug_report.md'
.format(line)
if len(stderr_lines) == 1:
# **Very likely** an error message that is independent of an error event
error_event = Event(
ResourceType.UNKNOWN,
'',
'Error',
line,
)
events.append(error_event)
continue
_warn_missing_dry_run_prefix(line, warn_missing_dry_run_prefix, warn_function)
_warn_unparsable_line(line, warn_function)
return events
@ -218,8 +323,18 @@ def has_changes(events):
def extract_actions(events):
actions = []
pull_actions = set()
for event in events:
if event.status in DOCKER_STATUS_WORKING:
if event.resource_type == ResourceType.IMAGE_LAYER and event.status in DOCKER_PULL_PROGRESS_WORKING:
pull_id = (event.resource_id, event.status)
if pull_id not in pull_actions:
pull_actions.add(pull_id)
actions.append({
'what': event.resource_type,
'id': event.resource_id,
'status': event.status,
})
if event.resource_type != ResourceType.IMAGE_LAYER and event.status in DOCKER_STATUS_WORKING:
actions.append({
'what': event.resource_type,
'id': event.resource_id,

View File

@ -0,0 +1,385 @@
#!/usr/bin/python
#
# Copyright (c) 2023, Felix Fontein <felix@fontein.de>
# 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 = '''
module: docker_compose_v2_pull
short_description: Pull a Docker compose project
version_added: 3.6.0
description:
- Uses Docker Compose to pull images for a project.
extends_documentation_fragment:
- community.docker.compose_v2
- community.docker.docker.cli_documentation
- community.docker.attributes
- community.docker.attributes.actiongroup_docker
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
policy:
description:
- Whether to pull images before running. This is used when C(docker compose up) is ran.
- V(always) ensures that the images are always pulled, even when already present on the Docker daemon.
- V(missing) only pulls them when they are not present on the Docker daemon. This is only supported since Docker Compose 2.22.0.
type: str
choices:
- always
- missing
default: always
requirements:
- "Docker CLI with Docker compose plugin 2.18.0 or later"
author:
- Felix Fontein (@felixfontein)
notes:
- |-
The Docker compose CLI plugin has no stable output format (see for example U(https://github.com/docker/compose/issues/10872)),
and for the main operations also no machine friendly output format. The module tries to accomodate this with various
version-dependent behavior adjustments and with testing older and newer versions of the Docker compose CLI plugin.
Currently the module is tested with multiple plugin versions between 2.18.1 and 2.23.3. The exact list of plugin versions
will change over time. New releases of the Docker compose CLI plugin can break this module at any time.
seealso:
- module: community.docker.docker_compose
'''
EXAMPLES = '''
# Examples use the django example at https://docs.docker.com/compose/django. Follow it to create the
# flask directory
- name: Run using a project directory
hosts: localhost
gather_facts: false
tasks:
- name: Tear down existing services
community.docker.docker_compose_v2:
project_src: flask
state: absent
- name: Create and start services
community.docker.docker_compose_v2:
project_src: flask
register: output
- name: Show results
ansible.builtin.debug:
var: output
- name: Run `docker-compose up` again
community.docker.docker_compose_v2:
project_src: flask
register: output
- name: Show results
ansible.builtin.debug:
var: output
- ansible.builtin.assert:
that: not output.changed
- name: Stop all services
community.docker.docker_compose_v2:
project_src: flask
state: stopped
register: output
- name: Show results
ansible.builtin.debug:
var: output
- name: Verify that web and db services are not running
ansible.builtin.assert:
that:
- "not output.services.web.flask_web_1.state.running"
- "not output.services.db.flask_db_1.state.running"
- name: Restart services
community.docker.docker_compose_v2:
project_src: flask
state: restarted
register: output
- name: Show results
ansible.builtin.debug:
var: output
- name: Verify that web and db services are running
ansible.builtin.assert:
that:
- "output.services.web.flask_web_1.state.running"
- "output.services.db.flask_db_1.state.running"
'''
RETURN = '''
containers:
description:
- A list of containers associated to the service.
returned: success
type: list
elements: dict
contains:
Command:
description:
- The container's command.
type: raw
CreatedAt:
description:
- The timestamp when the container was created.
type: str
sample: "2024-01-02 12:20:41 +0100 CET"
ExitCode:
description:
- The container's exit code.
type: int
Health:
description:
- The container's health check.
type: raw
ID:
description:
- The container's ID.
type: str
sample: "44a7d607219a60b7db0a4817fb3205dce46e91df2cb4b78a6100b6e27b0d3135"
Image:
description:
- The container's image.
type: str
Labels:
description:
- Labels for this container.
type: dict
LocalVolumes:
description:
- The local volumes count.
type: str
Mounts:
description:
- Mounts.
type: str
Name:
description:
- The container's primary name.
type: str
Names:
description:
- List of names of the container.
type: list
elements: str
Networks:
description:
- List of networks attached to this container.
type: list
elements: str
Ports:
description:
- List of port assignments as a string.
type: str
Publishers:
description:
- List of port assigments.
type: list
elements: dict
contains:
URL:
description:
- Interface the port is bound to.
type: str
TargetPort:
description:
- The container's port the published port maps to.
type: int
PublishedPort:
description:
- The port that is published.
type: int
Protocol:
description:
- The protocol.
type: str
choices:
- tcp
- udp
RunningFor:
description:
- Amount of time the container runs.
type: str
Service:
description:
- The name of the service.
type: str
Size:
description:
- The container's size.
type: str
sample: "0B"
State:
description:
- The container's state.
type: str
sample: running
Status:
description:
- The container's status.
type: str
sample: Up About a minute
images:
description:
- A list of images associated to the service.
returned: success
type: list
elements: dict
contains:
ID:
description:
- The image's ID.
type: str
sample: sha256:c8bccc0af9571ec0d006a43acb5a8d08c4ce42b6cc7194dd6eb167976f501ef1
ContainerName:
description:
- Name of the conainer this image is used by.
type: str
Repository:
description:
- The repository where this image belongs to.
type: str
Tag:
description:
- The tag of the image.
type: str
Size:
description:
- The image's size in bytes.
type: int
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:
- container
- image
- network
- service
- unknown
- volume
id:
description:
- The ID of the resource that was changed.
type: str
sample: container
status:
description:
- The status change that happened.
type: str
sample: Creating
choices:
- Starting
- Exiting
- Restarting
- Creating
- Stopping
- Killing
- Removing
- Recreating
- Pulling
'''
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,
)
from ansible_collections.community.docker.plugins.module_utils.version import LooseVersion
DOCKER_COMPOSE_MINIMAL_VERSION = '2.18.0'
class PullManager(BaseComposeManager):
def __init__(self, client):
super(PullManager, self).__init__(client, min_version=DOCKER_COMPOSE_MINIMAL_VERSION)
parameters = self.client.module.params
self.policy = parameters['policy']
if self.policy != 'always' and self.compose_version < LooseVersion('2.22.0'):
# https://github.com/docker/compose/pull/10981 - 2.22.0
self.client.fail('A pull policy other than always is only supported since Docker Compose 2.22.0. {0} has version {1}'.format(
self.client.get_cli(), self.compose_version))
def get_pull_cmd(self, dry_run, no_start=False):
args = self.get_base_args() + ['pull']
if self.policy != 'always':
args.extend(['--policy', self.policy])
if dry_run:
args.append('--dry-run')
args.append('--')
return args
def run(self):
result = dict()
args = self.get_pull_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)
self.emit_warnings(events)
self.update_result(result, events, stdout, stderr)
self.update_failed(result, events, args, stdout, stderr, rc)
self.cleanup_result(result)
return result
def main():
argument_spec = dict(
policy=dict(type='str', choices=['always', 'missing'], default='always'),
)
argument_spec.update(common_compose_argspec())
client = AnsibleModuleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
)
try:
result = PullManager(client).run()
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()

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,48 @@
---
# 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: []
dnetworks: []
- debug:
msg: "Using name prefix {{ name_prefix }}"
# Run the tests
- block:
- 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 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', '>=')

View File

@ -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 }}"

View File

@ -0,0 +1,187 @@
---
# 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 }}-pull"
cname: "{{ name_prefix }}-cont"
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_alpine: |
version: '3'
services:
{{ cname }}:
image: {{ docker_test_image_alpine }}
command: /bin/sh -c 'sleep 10m'
stop_grace_period: 1s
block:
- name: Registering container name
set_fact:
cnames: "{{ cnames + [pname ~ '-' ~ cname ~ '-1'] }}"
dnetworks: "{{ dnetworks + [pname ~ '_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 }}'
- '{{ docker_test_image_alpine }}'
####################################################################
## Missing image ###################################################
####################################################################
- name: Template project file with non-existing image
copy:
dest: '{{ project_src }}/docker-compose.yml'
content: '{{ test_service_non_existing }}'
- name: Pull (check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
check_mode: true
register: pull_1_check
ignore_errors: true
- name: Pull
docker_compose_v2_pull:
project_src: '{{ project_src }}'
register: pull_1
ignore_errors: true
- assert:
that:
- pull_1_check is failed or pull_1_check is changed
- pull_1_check is changed or pull_1_check.msg.startswith('Error when processing ')
- pull_1 is failed
- pull_1.msg.startswith('Error when processing ')
####################################################################
## Regular image ###################################################
####################################################################
- name: Template project file with Alpine image
copy:
dest: '{{ project_src }}/docker-compose.yml'
content: '{{ test_service_alpine }}'
- when: docker_compose_version is version('2.22.0', '>=')
block:
- name: Pull with policy=missing (check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: missing
check_mode: true
register: pull_1_check
- name: Pull with policy=missing
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: missing
register: pull_1
- name: Pull with policy=missing (idempotent, check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: missing
check_mode: true
register: pull_2_check
- name: Pull with policy=missing (idempotent)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: missing
register: pull_2
- name: Pull with policy=always (check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
check_mode: true
register: pull_3_check
- name: Pull with policy=always
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
register: pull_3
- assert:
that:
- pull_1_check is changed
- pull_1_check.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_1_check.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_1_check.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_1 is changed
- pull_1.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_1.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_1.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_2_check is not changed
- pull_2 is not changed
- pull_3_check is changed
- pull_3_check.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_3_check.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_3_check.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_3 is changed
- pull_3.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_3.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_3.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- when: docker_compose_version is version('2.22.0', '<')
block:
- name: Pull with policy=always (check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
check_mode: true
register: pull_1_check
- name: Pull with policy=always
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
register: pull_1
- name: Pull with policy=always (again, check)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
check_mode: true
register: pull_2_check
- name: Pull with policy=always (again)
docker_compose_v2_pull:
project_src: '{{ project_src }}'
policy: always
register: pull_2
- assert:
that:
- pull_1_check is changed
- pull_1_check.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_1_check.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_1_check.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_1 is changed
- pull_1.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_1.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_1.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_2_check is changed
- pull_2_check.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_2_check.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_2_check.actions | selectattr('status', 'eq', 'Recreating') | length == 0
- pull_2 is changed
- pull_2.actions | selectattr('status', 'eq', 'Pulling') | first
- pull_2.actions | selectattr('status', 'eq', 'Creating') | length == 0
- pull_2.actions | selectattr('status', 'eq', 'Recreating') | length == 0

View File

@ -8,5 +8,6 @@ plugins/modules/current_container_facts.py validate-modules:return-syntax-error
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
plugins/modules/docker_compose_v2.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2_pull.py validate-modules:return-syntax-error
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin

View File

@ -8,5 +8,6 @@ plugins/modules/current_container_facts.py validate-modules:return-syntax-error
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
plugins/modules/docker_compose_v2.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2_pull.py validate-modules:return-syntax-error
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin

View File

@ -1,4 +1,5 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/modules/current_container_facts.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2_pull.py validate-modules:return-syntax-error
plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin

View File

@ -1,3 +1,4 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/modules/docker_compose_v2.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2_pull.py validate-modules:return-syntax-error
plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin

View File

@ -7,5 +7,6 @@
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
plugins/modules/docker_compose_v2.py validate-modules:return-syntax-error
plugins/modules/docker_compose_v2_pull.py validate-modules:return-syntax-error
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
plugins/modules/docker_container_copy_into.py validate-modules:undocumented-parameter # _max_file_size_for_diff is used by the action plugin