Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package ansible-core for openSUSE:Factory checked in at 2026-07-14 13:51:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core (Old) and /work/SRC/openSUSE:Factory/.ansible-core.new.1991 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core" Tue Jul 14 13:51:32 2026 rev:62 rq:1365472 version:2.21.2 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes 2026-06-19 17:12:22.691488324 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.1991/ansible-core.changes 2026-07-14 13:51:53.823034133 +0200 @@ -1,0 +2,18 @@ +Tue Jul 14 05:15:55 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 2.21.2: + * Minor Changes + - ansible-test - Added a timeout callback that dumps thread + stacks when the test execution deadline defined by + ansible-test env --timeout is approaching. + - parallel fact gathering - the async wrapper now considers the + timeout when determining whether to kill the process running + the module. Previously, a 5 second sleep occurred twice + before checking if the job had remaining time. + * Bugfixes + - encrypt - fix bcrypt salt string formatting on musl libc by + ensuring it is always zero-padded to 2 digits (#87180). + - parallel fact gathering - fix hang caused by corrupt async + job files. + +------------------------------------------------------------------- Old: ---- ansible_core-2.21.1.tar.gz ansible_core-2.21.1.tar.gz.sha256 New: ---- ansible_core-2.21.2.tar.gz ansible_core-2.21.2.tar.gz.sha256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core.spec ++++++ --- /var/tmp/diff_new_pack.5alxEK/_old 2026-07-14 13:51:55.827103426 +0200 +++ /var/tmp/diff_new_pack.5alxEK/_new 2026-07-14 13:51:55.831103564 +0200 @@ -43,7 +43,7 @@ %endif Name: ansible-core -Version: 2.21.1 +Version: 2.21.2 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later ++++++ ansible_core-2.21.1.tar.gz -> ansible_core-2.21.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/PKG-INFO new/ansible_core-2.21.2/PKG-INFO --- old/ansible_core-2.21.1/PKG-INFO 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/PKG-INFO 2026-07-13 21:37:14.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.21.1 +Version: 2.21.2 Summary: Radically simple IT automation Author: Ansible Project License-Expression: GPL-3.0-or-later diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/ansible_core.egg-info/PKG-INFO new/ansible_core-2.21.2/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.21.1/ansible_core.egg-info/PKG-INFO 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/ansible_core.egg-info/PKG-INFO 2026-07-13 21:37:14.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.21.1 +Version: 2.21.2 Summary: Radically simple IT automation Author: Ansible Project License-Expression: GPL-3.0-or-later diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.21.2/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.21.1/ansible_core.egg-info/SOURCES.txt 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/ansible_core.egg-info/SOURCES.txt 2026-07-13 21:37:14.000000000 +0200 @@ -1366,6 +1366,14 @@ test/integration/targets/ansible-test-shell/expected-stdout.txt test/integration/targets/ansible-test-shell/runme.sh test/integration/targets/ansible-test-shell/ansible_collections/ns/col/.keep +test/integration/targets/ansible-test-timeout/aliases +test/integration/targets/ansible-test-timeout/runme.sh +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/aliases +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/runme.sh +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/aliases +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/tasks/main.yml +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/aliases +test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/runme.sh test/integration/targets/ansible-test-units/aliases test/integration/targets/ansible-test-units/runme.sh test/integration/targets/ansible-test-units-assertions/aliases @@ -4760,6 +4768,7 @@ test/lib/ansible_test/_internal/provider/source/unsupported.py test/lib/ansible_test/_internal/provider/source/unversioned.py test/lib/ansible_test/_util/__init__.py +test/lib/ansible_test/_util/controller/ansible_collections/ansible_test/_internal/plugins/callback/timeout.py test/lib/ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.json test/lib/ansible_test/_util/controller/sanity/code-smell/action-plugin-docs.py test/lib/ansible_test/_util/controller/sanity/code-smell/changelog.json diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/changelogs/CHANGELOG-v2.21.rst new/ansible_core-2.21.2/changelogs/CHANGELOG-v2.21.rst --- old/ansible_core-2.21.1/changelogs/CHANGELOG-v2.21.rst 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/changelogs/CHANGELOG-v2.21.rst 2026-07-13 21:37:14.000000000 +0200 @@ -4,6 +4,27 @@ .. contents:: Topics +v2.21.2 +======= + +Release Summary +--------------- + +| Release Date: 2026-07-13 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__ + +Minor Changes +------------- + +- ansible-test - Added a timeout callback that dumps thread stacks when the test execution deadline defined by ``ansible-test env --timeout`` is approaching. +- parallel fact gathering - the async wrapper now considers the timeout when determining whether to kill the process running the module. Previously, a 5 second sleep occurred twice before checking if the job had remaining time. + +Bugfixes +-------- + +- encrypt - fix bcrypt salt string formatting on musl libc by ensuring it is always zero-padded to 2 digits (https://github.com/ansible/ansible/issues/87180). +- parallel fact gathering - fix hang caused by corrupt async job files. + v2.21.1 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/changelogs/changelog.yaml new/ansible_core-2.21.2/changelogs/changelog.yaml --- old/ansible_core-2.21.1/changelogs/changelog.yaml 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/changelogs/changelog.yaml 2026-07-13 21:37:14.000000000 +0200 @@ -692,3 +692,38 @@ - sort_obfuscation.yml - winrm-psrp-nolog.yml release_date: '2026-06-11' + 2.21.2: + changes: + release_summary: '| Release Date: 2026-07-13 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__ + + ' + codename: The Rain Song + fragments: + - 2.21.2_summary.yaml + release_date: '2026-07-13' + 2.21.2rc1: + changes: + bugfixes: + - encrypt - fix bcrypt salt string formatting on musl libc by ensuring it is + always zero-padded to 2 digits (https://github.com/ansible/ansible/issues/87180). + - parallel fact gathering - fix hang caused by corrupt async job files. + minor_changes: + - ansible-test - Added a timeout callback that dumps thread stacks when the + test execution deadline defined by ``ansible-test env --timeout`` is approaching. + - parallel fact gathering - the async wrapper now considers the timeout when + determining whether to kill the process running the module. Previously, a + 5 second sleep occurred twice before checking if the job had remaining time. + release_summary: '| Release Date: 2026-07-06 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__ + + ' + codename: The Rain Song + fragments: + - 2.21.2rc1_summary.yaml + - 87180-crypt-fixes.yml + - ansible-test-timeout-callback.yml + - fix-short-timeout-async-wrapper.yml + release_date: '2026-07-06' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/lib/ansible/_internal/_encryption/_crypt.py new/ansible_core-2.21.2/lib/ansible/_internal/_encryption/_crypt.py --- old/ansible_core-2.21.1/lib/ansible/_internal/_encryption/_crypt.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/lib/ansible/_internal/_encryption/_crypt.py 2026-07-13 21:37:14.000000000 +0200 @@ -12,7 +12,7 @@ __all__ = ['CryptFacade'] -_FAILURE_TOKENS = frozenset({b'*0', b'*1'}) +_FAILURE_TOKENS = frozenset({b'*', b'*0', b'*1'}) @dataclass(frozen=True) @@ -93,10 +93,9 @@ if self._use_crypt_r: self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(self._CryptData)] - self._crypt_impl.restype = ctypes.c_char_p else: self._crypt_impl.argtypes = [ctypes.c_char_p, ctypes.c_char_p] - self._crypt_impl.restype = ctypes.c_char_p + self._crypt_impl.restype = ctypes.c_char_p # Try to load crypt_gensalt (available in libxcrypt) try: @@ -133,10 +132,7 @@ error_msg = os.strerror(errno) raise OSError(errno, f'crypt failed: {error_msg}') - if result is None: - raise ValueError('crypt failed: invalid salt or unsupported algorithm') - - if result in _FAILURE_TOKENS: + if result is None or result in _FAILURE_TOKENS: raise ValueError('crypt failed: invalid salt or unsupported algorithm') return result diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.21.2/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.21.1/lib/ansible/module_utils/ansible_release.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/lib/ansible/module_utils/ansible_release.py 2026-07-13 21:37:14.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.21.1' +__version__ = '2.21.2' __author__ = 'Ansible, Inc.' __codename__ = "The Rain Song" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/lib/ansible/modules/async_wrapper.py new/ansible_core-2.21.2/lib/ansible/modules/async_wrapper.py --- old/ansible_core-2.21.1/lib/ansible/modules/async_wrapper.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/lib/ansible/modules/async_wrapper.py 2026-07-13 21:37:14.000000000 +0200 @@ -291,24 +291,31 @@ os.setpgid(sub_pid, sub_pid) notice("Start watching %s (%s)" % (sub_pid, remaining)) + + if remaining != 0 and step >= remaining: + step = remaining / 2 + time.sleep(step) + while os.waitpid(sub_pid, os.WNOHANG) == (0, 0): - notice("%s still running (%s)" % (sub_pid, remaining)) - time.sleep(step) remaining = remaining - step + notice("%s still running (%s)" % (sub_pid, remaining)) if remaining <= 0: - # ensure we leave response in poll location - res = {'msg': 'Timeout exceeded', 'failed': True, 'child_pid': sub_pid} - jwrite(res) - # actually kill it notice("Timeout reached, now killing %s" % (sub_pid)) os.killpg(sub_pid, signal.SIGKILL) notice("Sent kill to group %s " % sub_pid) time.sleep(1) + + # ensure we leave response in poll location + res = {'msg': 'Timeout exceeded', 'failed': True, 'child_pid': sub_pid} + jwrite(res) + if not preserve_tmp: shutil.rmtree(os.path.dirname(wrapped_module), True) end(res) + + time.sleep(step) notice("Done in kid B.") if not preserve_tmp: shutil.rmtree(os.path.dirname(wrapped_module), True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/lib/ansible/release.py new/ansible_core-2.21.2/lib/ansible/release.py --- old/ansible_core-2.21.1/lib/ansible/release.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/lib/ansible/release.py 2026-07-13 21:37:14.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.21.1' +__version__ = '2.21.2' __author__ = 'Ansible, Inc.' __codename__ = "The Rain Song" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/lib/ansible/utils/encrypt.py new/ansible_core-2.21.2/lib/ansible/utils/encrypt.py --- old/ansible_core-2.21.1/lib/ansible/utils/encrypt.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/lib/ansible/utils/encrypt.py 2026-07-13 21:37:14.000000000 +0200 @@ -186,7 +186,8 @@ saltstring = f'${ident}' if ident else '' if rounds: if self.algo_data.rounds_format == 'cost': - saltstring += f'${rounds}' + # musl libc requires a 2-digit bcrypt cost ($2b$05$, not $2b$5$). + saltstring += f'${rounds:02d}' else: saltstring += f'$rounds={rounds}' saltstring += f'${salt}' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/pyproject.toml new/ansible_core-2.21.2/pyproject.toml --- old/ansible_core-2.21.1/pyproject.toml 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/pyproject.toml 2026-07-13 21:37:14.000000000 +0200 @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 77.0.3, <= 82.0.1"] # lower bound to support license/license-files (PEP 639), upper bound for latest version tested at release +requires = ["setuptools >= 77.0.3, <= 83.0.0"] # lower bound to support license/license-files (PEP 639), upper bound for latest version tested at release build-backend = "setuptools.build_meta" [project] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-container/runme.py new/ansible_core-2.21.2/test/integration/targets/ansible-test-container/runme.py --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-container/runme.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-container/runme.py 2026-07-13 21:37:14.000000000 +0200 @@ -1007,6 +1007,9 @@ if cls.install_docker(): packages.append('moby-engine') + if cls.install_docker() and os_release.id == 'fedora': + packages.append('runc') + if os_release.id == 'rhel': # As of the release of RHEL 9.1, installing podman on RHEL 9.0 results in a non-fatal error at install time: # diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/aliases new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/aliases --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/aliases 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/aliases 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,4 @@ +shippable/posix/group3 # runs in the distro test containers +shippable/generic/group1 # runs in the default test container +context/controller +needs/target/collection diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/aliases new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/aliases --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/aliases 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/aliases 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1 @@ +context/controller diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/runme.sh new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/runme.sh --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/runme.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/noop/runme.sh 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +set -eux diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/aliases new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/aliases --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/aliases 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/aliases 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,2 @@ +context/controller +gather_facts/no diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/tasks/main.yml new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/tasks/main.yml --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/tasks/main.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/pause/tasks/main.yml 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,3 @@ +- name: Wait long enough for the timeout callback to fire + wait_for: + timeout: 30 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/aliases new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/aliases --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/aliases 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/aliases 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1 @@ +context/controller diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/runme.sh new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/runme.sh --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/runme.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/ansible_collections/ns/col/tests/integration/targets/sleep/runme.sh 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eux + +sleep 30 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/runme.sh new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/runme.sh --- old/ansible_core-2.21.1/test/integration/targets/ansible-test-timeout/runme.sh 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/integration/targets/ansible-test-timeout/runme.sh 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Test ansible-test timeout functionality and the timeout callback. + +source ../collection/setup.sh + +set -eux -o pipefail + +# Prime the venv so timeouts don't risk leaving a broken venv. +ansible-test integration noop --venv --color --truncate 0 + +echo "=== Test 1: ansible-test timeout already expired ===" +ansible-test env --timeout 0.0001 2>&1 | tee test1-env.txt + +if ansible-test integration sleep --venv --color --truncate 0 2>&1 | tee test1.txt; then + echo "FAIL: expected ansible-test to fail due to expired timeout, but it passed." + exit 1 +fi + +grep -q "test timeout expired" test1.txt +grep -q "timeout may be too short" test1-env.txt + +echo "=== Test 2: ansible-test timeout fires during a test ===" +# Use the sleep target (a shell script) so ansible-playbook doesn't run +# and the timeout callback doesn't intercept. +ansible-test env --timeout 0.1 + +if ansible-test integration sleep --venv --color --truncate 0 2>&1 | tee test2.txt; then + echo "FAIL: expected ansible-test to fail due to timeout, but it passed." + exit 1 +fi + +grep -q "time limit" test2.txt + +echo "=== Test 3: timeout callback with deadline already passed ===" +# The timeout margin equals the timeout duration, so the callback +# deadline is guaranteed to be in the past by the time it loads. +# ansible-test's own timeout check passes since the deadline itself hasn't +# expired yet at startup. +ansible-test env --timeout 0.167 2>&1 | tee test3-env.txt + +if ansible-test integration pause --venv --color --truncate 0 2>&1 | tee test3.txt; then + echo "FAIL: expected ansible-test to fail due to timeout traceback, but it passed." + exit 1 +fi + +grep -q "Tests aborted by the timeout callback" test3.txt +grep -q "deadline exceeded by" test3.txt +grep -q "timeout may be too short" test3-env.txt && exit 1 + +echo "=== Test 4: timeout callback fires while process is hung ===" +# Use a longer timeout so the callback deadline is in the future when it +# loads. The pause target keeps ansible-playbook alive long enough for the +# timeout callback to fire. +ansible-test env --timeout 0.25 + +if ansible-test integration pause --venv --color --truncate 0 2>&1 | tee test4.txt; then + echo "FAIL: expected ansible-test to fail due to timeout traceback, but it passed." + exit 1 +fi + +grep -q "Tests aborted by the timeout callback" test4.txt +grep -q "remaining" test4.txt + +echo PASS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/collection/setup.sh new/ansible_core-2.21.2/test/integration/targets/collection/setup.sh --- old/ansible_core-2.21.1/test/integration/targets/collection/setup.sh 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/integration/targets/collection/setup.sh 2026-07-13 21:37:14.000000000 +0200 @@ -16,9 +16,6 @@ # 4) Windows tests need access to the ansible.windows vendored collection. # This script copies any of the existing collections in ANSIBLE_COLLECTIONS_PATH to the temporary directory. # -# NOTE: This script is *NOT* compatible ansible-test's built-in collection injection feature, -# since it assumes that ANSIBLE_COLLECTIONS_PATH contains only a single path. - set -eu -o pipefail export TEST_DIR @@ -33,7 +30,13 @@ cd "${WORK_DIR}/ansible_collections/ns/${COLLECTION_NAME:-col}" if [ "${ANSIBLE_COLLECTIONS_PATH:+set}" = "set" ]; then - cp -aL "${ANSIBLE_COLLECTIONS_PATH}"/ansible_collections/* "${WORK_DIR}/ansible_collections" + IFS=':' read -ra collections_paths <<< "${ANSIBLE_COLLECTIONS_PATH}" + + for collections_path in "${collections_paths[@]}"; do + if [ -d "${collections_path}/ansible_collections" ]; then + cp -aL "${collections_path}"/ansible_collections/* "${WORK_DIR}/ansible_collections" + fi + done fi "${TEST_DIR}/../collection/update-ignore.py" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/gathering_facts/library/slow new/ansible_core-2.21.2/test/integration/targets/gathering_facts/library/slow --- old/ansible_core-2.21.1/test/integration/targets/gathering_facts/library/slow 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/integration/targets/gathering_facts/library/slow 2026-07-13 21:37:14.000000000 +0200 @@ -1,6 +1,6 @@ #!/bin/sh -sleep 10 +sleep 3 echo '{ "changed": false, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/integration/targets/gathering_facts/runme.sh new/ansible_core-2.21.2/test/integration/targets/gathering_facts/runme.sh --- old/ansible_core-2.21.1/test/integration/targets/gathering_facts/runme.sh 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/integration/targets/gathering_facts/runme.sh 2026-07-13 21:37:14.000000000 +0200 @@ -15,7 +15,7 @@ ansible-playbook test_prevent_injection.yml -i inventory -v "$@" # ensure fact merging is working properly -ansible-playbook verify_merge_facts.yml -v "$@" -e 'ansible_facts_parallel: False' +ansible-playbook verify_merge_facts.yml -v "$@" -e 'ansible_facts_parallel=False' # ensure we dont clobber facts in loop ansible-playbook prevent_clobbering.yml -v "$@" @@ -36,10 +36,10 @@ ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ "$@" # test that gather_facts will timeout parallel modules that dont support gather_timeout when using gather_Timeout -ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=1 parallel=true' "$@" 2>&1 |grep 'Timeout exceeded' +ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ANSIBLE_TASK_TIMEOUT=5 ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=1 parallel=true' "$@" 2>&1 |grep 'Timeout exceeded' # test that gather_facts parallel w/o timing out -ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=30 parallel=true' "$@" 2>&1 |grep -v 'Timeout exceeded' +ANSIBLE_FACTS_MODULES='ansible.legacy.slow' ansible -m gather_facts localhost --playbook-dir ./ -a 'gather_timeout=10 parallel=true' "$@" 2>&1 |grep 'Timeout exceeded' && exit 1 # test parallelism diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/lib/ansible_test/_internal/commands/env/__init__.py new/ansible_core-2.21.2/test/lib/ansible_test/_internal/commands/env/__init__.py --- old/ansible_core-2.21.1/test/lib/ansible_test/_internal/commands/env/__init__.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/lib/ansible_test/_internal/commands/env/__init__.py 2026-07-13 21:37:14.000000000 +0200 @@ -36,6 +36,7 @@ ) from ...constants import ( + TIMEOUT_MARGIN_SECONDS, TIMEOUT_PATH, ) @@ -137,6 +138,9 @@ if timeout: display.info(f'Setting a {timeout.duration} minute test timeout which will end at: {timeout.deadline}', verbosity=1) + + if timeout.duration * 60 <= TIMEOUT_MARGIN_SECONDS: + display.warning(f'The {timeout.duration} minute timeout may be too short for the faulthandler callback to provide useful diagnostics.') else: display.info('Clearing existing test timeout.', verbosity=1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/lib/ansible_test/_internal/commands/integration/__init__.py new/ansible_core-2.21.2/test/lib/ansible_test/_internal/commands/integration/__init__.py --- old/ansible_core-2.21.1/test/lib/ansible_test/_internal/commands/integration/__init__.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/lib/ansible_test/_internal/commands/integration/__init__.py 2026-07-13 21:37:14.000000000 +0200 @@ -8,6 +8,7 @@ import datetime import json import os +import pathlib import re import shutil import tempfile @@ -57,7 +58,12 @@ read_text_file, ) +from ...constants import ( + TIMEOUT_MARGIN_SECONDS, +) + from ...util import ( + ANSIBLE_TEST_CONTROLLER_ROOT, ApplicationError, display, get_ansible_version, @@ -133,6 +139,10 @@ CoverageManager, ) +from ...timeout import ( + get_timeout, +) + def generate_dependency_map(integration_targets: list[IntegrationTarget]) -> dict[str, set[IntegrationTarget]]: """Analyze the given list of integration test targets and return a dictionary expressing target names and the targets on which they depend.""" @@ -306,7 +316,11 @@ ansible_config = ansible_config_src vars_file = os.path.join(data_context().content.root, data_context().content.integration_vars_path) - yield IntegrationEnvironment(data_context().content.root, integration_dir, targets_dir, inventory_path, ansible_config, vars_file) + test_env = IntegrationEnvironment(data_context().content.root, integration_dir, targets_dir, inventory_path, ansible_config, vars_file) + + with test_env.run(): + yield test_env + return # When testing a collection, the temporary directory must reside within the collection. @@ -385,7 +399,10 @@ make_dirs(os.path.dirname(file_dst)) shutil.copy2(file_src, file_dst) - yield IntegrationEnvironment(temp_dir, integration_dir, targets_dir, inventory_path, ansible_config, vars_file, collection_roots) + test_env = IntegrationEnvironment(temp_dir, integration_dir, targets_dir, inventory_path, ansible_config, vars_file, collection_roots) + + with test_env.run(): + yield test_env finally: if not args.explain: remove_tree(temp_dir) @@ -850,6 +867,11 @@ callback_plugins = ['junit'] + (env_config.callback_plugins or [] if env_config else []) + timeout = get_timeout() + + if timeout: + callback_plugins.append('ansible_test._internal.timeout') + integration = dict( JUNIT_OUTPUT_DIR=ResultType.JUNIT.path, JUNIT_TASK_RELATIVE_PATH=test_env.test_dir, @@ -862,6 +884,16 @@ INVENTORY_PATH=os.path.abspath(inventory_path), ) + if timeout: + collections_path = env.get('ANSIBLE_COLLECTIONS_PATH', '') + collections_path = f'{ANSIBLE_TEST_CONTROLLER_ROOT}:{collections_path}' if collections_path else ANSIBLE_TEST_CONTROLLER_ROOT + + integration.update( + ANSIBLE_COLLECTIONS_PATH=collections_path, + ANSIBLE_TEST_TIMEOUT_DEADLINE=str(timeout.deadline.timestamp() - TIMEOUT_MARGIN_SECONDS), + ANSIBLE_TEST_TIMEOUT_DIR=str(test_env.timeout_dir), + ) + if args.debug_strategy: env.update(ANSIBLE_STRATEGY='debug') @@ -892,6 +924,44 @@ vars_file: str collection_roots: list[str] = dataclasses.field(default_factory=list) + @property + def timeout_dir(self) -> pathlib.Path: + """Return the path for timeout dump files.""" + return pathlib.Path(self.test_dir) / 'timeout' + + @contextlib.contextmanager + def run(self) -> c.Iterator[None]: + """Context manager that checks for timeout dumps after the test runs.""" + try: + yield + finally: + self.check_timeout_dumps() + + def check_timeout_dumps(self) -> None: + """Check for timeout dump files and raise an error if any contain tracebacks.""" + if not (timeout := get_timeout()): + return + + if not self.timeout_dir.is_dir(): + return + + found = False + + for dump_file in sorted(self.timeout_dir.iterdir()): + lines = dump_file.read_text().splitlines() + + if len(lines) <= 2: # first line is the command, second is the timeout status, traceback, if any, follows + continue + + found = True + display.error(f'Timeout traceback detected ({dump_file.name}):\n' + '\n'.join(lines)) + + if found: + raise ApplicationError( + f'Tests aborted by the timeout callback after approaching the {timeout.duration} minute time limit.' + ' See traceback(s) above for process state.' + ) + def update_environment(self, env: dict[str, str]) -> None: """Update the given environment dictionary with the variables necessary for this integration environment.""" collections_path = value.split(':') if (value := env.get('ANSIBLE_COLLECTIONS_PATH')) else [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/lib/ansible_test/_internal/constants.py new/ansible_core-2.21.2/test/lib/ansible_test/_internal/constants.py --- old/ansible_core-2.21.1/test/lib/ansible_test/_internal/constants.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/lib/ansible_test/_internal/constants.py 2026-07-13 21:37:14.000000000 +0200 @@ -15,6 +15,7 @@ # File used to track the ansible-test test execution timeout. TIMEOUT_PATH = '.ansible-test-timeout.json' +TIMEOUT_MARGIN_SECONDS = 10 CONTROLLER_MIN_PYTHON_VERSION = CONTROLLER_PYTHON_VERSIONS[0] SUPPORTED_PYTHON_VERSIONS = REMOTE_ONLY_PYTHON_VERSIONS + CONTROLLER_PYTHON_VERSIONS diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/lib/ansible_test/_internal/timeout.py new/ansible_core-2.21.2/test/lib/ansible_test/_internal/timeout.py --- old/ansible_core-2.21.1/test/lib/ansible_test/_internal/timeout.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/lib/ansible_test/_internal/timeout.py 2026-07-13 21:37:14.000000000 +0200 @@ -20,6 +20,7 @@ ) from .util import ( + cache, display, TimeoutExpiredError, ) @@ -81,6 +82,7 @@ ) +@cache def get_timeout() -> TimeoutDetail | None: """Return details about the currently set timeout, if any, otherwise return None.""" try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/lib/ansible_test/_util/controller/ansible_collections/ansible_test/_internal/plugins/callback/timeout.py new/ansible_core-2.21.2/test/lib/ansible_test/_util/controller/ansible_collections/ansible_test/_internal/plugins/callback/timeout.py --- old/ansible_core-2.21.1/test/lib/ansible_test/_util/controller/ansible_collections/ansible_test/_internal/plugins/callback/timeout.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.2/test/lib/ansible_test/_util/controller/ansible_collections/ansible_test/_internal/plugins/callback/timeout.py 2026-07-13 21:37:14.000000000 +0200 @@ -0,0 +1,73 @@ +"""Ansible callback plugin to enforce an execution deadline using faulthandler.""" +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +import datetime +import faulthandler +import os +import pathlib +import shlex +import sys + +from ansible.plugins.callback import CallbackBase + +DOCUMENTATION = """ + name: timeout + type: aggregate + short_description: Dump thread stacks when an execution deadline is reached. + description: + - Enforces an execution deadline by dumping all thread stacks to a file + and terminating the process when the deadline expires. + - Intended for use by ansible-test only. Do not enable manually. + options: + deadline: + description: Execution deadline as a UTC epoch timestamp. + env: + - name: ANSIBLE_TEST_TIMEOUT_DEADLINE + dump_dir: + description: Directory for timeout dump files. + env: + - name: ANSIBLE_TEST_TIMEOUT_DIR +""" + + +class CallbackModule(CallbackBase): + """Dump thread stacks and exit when an execution deadline is reached.""" + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'aggregate' + CALLBACK_NAME = 'timeout' + CALLBACK_NEEDS_ENABLED = True + + def __init__(self): + super().__init__() + + deadline_str = os.environ.get('ANSIBLE_TEST_TIMEOUT_DEADLINE') + dump_dir = os.environ.get('ANSIBLE_TEST_TIMEOUT_DIR') + + if not deadline_str or not dump_dir: + return + + deadline = datetime.datetime.fromtimestamp(float(deadline_str), tz=datetime.timezone.utc) + now = datetime.datetime.now(datetime.timezone.utc) + remaining = deadline - now + + dump_dir_path = pathlib.Path(dump_dir) + dump_dir_path.mkdir(parents=True, exist_ok=True) + + dump_path = dump_dir_path / f'timeout-{now.isoformat()}-{os.getpid()}.txt' + + self._dump_file = dump_path.open('w') + self._dump_file.write(f'Command: {shlex.join(sys.argv).replace("\n", " ")}\n') + self._dump_file.flush() + + if remaining > datetime.timedelta(): + self._dump_file.write(f'Timeout: {remaining} remaining\n') + self._dump_file.flush() + faulthandler.dump_traceback_later(remaining.total_seconds(), file=self._dump_file, exit=True) + else: + self._dump_file.write(f'Timeout: deadline exceeded by {-remaining}\n') + self._dump_file.flush() + faulthandler.dump_traceback(file=self._dump_file) + sys.exit(1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/units/_internal/_encryption/test_crypt.py new/ansible_core-2.21.2/test/units/_internal/_encryption/test_crypt.py --- old/ansible_core-2.21.1/test/units/_internal/_encryption/test_crypt.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/units/_internal/_encryption/test_crypt.py 2026-07-13 21:37:14.000000000 +0200 @@ -82,7 +82,7 @@ def test_crypt_fail_errno(self, mocker: MockerFixture) -> None: """Test crypt() setting failure errno raises OSError.""" - mocker.patch('ctypes.get_errno', return_value=errno.EBADFD) + mocker.patch('ctypes.get_errno', return_value=errno.EBADF) crypt_facade = CryptFacade() with pytest.raises(OSError, match=r'crypt failed:'): @@ -96,10 +96,11 @@ with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'): crypt_facade.crypt(b"test", b"123") - def test_crypt_result_failure(self, mocker: MockerFixture) -> None: + @pytest.mark.parametrize('failure_token', sorted(list(_FAILURE_TOKENS))) + def test_crypt_result_failure(self, mocker: MockerFixture, failure_token: bytes) -> None: """Test crypt() implementation returning failure token raises ValueError.""" crypt_facade = CryptFacade() - mocker.patch.object(crypt_facade, '_crypt_impl', return_value=list(_FAILURE_TOKENS)[0]) + mocker.patch.object(crypt_facade, '_crypt_impl', return_value=failure_token) with pytest.raises(ValueError, match=r'crypt failed: invalid salt or unsupported algorithm'): crypt_facade.crypt(b"test", b"123") @@ -124,7 +125,7 @@ def test_crypt_gensalt_fail_errno(self, mocker: MockerFixture) -> None: """Test crypt_gensalt() setting failure errno raises OSError.""" - mocker.patch('ctypes.get_errno', return_value=errno.EBADFD) + mocker.patch('ctypes.get_errno', return_value=errno.EBADF) crypt_facade = CryptFacade() with pytest.raises(OSError, match=r'crypt_gensalt failed:'): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.1/test/units/utils/test_encrypt.py new/ansible_core-2.21.2/test/units/utils/test_encrypt.py --- old/ansible_core-2.21.1/test/units/utils/test_encrypt.py 2026-06-18 21:33:01.000000000 +0200 +++ new/ansible_core-2.21.2/test/units/utils/test_encrypt.py 2026-07-13 21:37:14.000000000 +0200 @@ -294,6 +294,16 @@ with pytest.raises(AnsibleError, match=r"crypt does not support 'sha256_crypt' algorithm"): crypt_hash.hash("123", salt="12345678") + def test_build_saltstring_bcrypt_cost_padding(self, mocker: MockerFixture) -> None: + """musl libc requires a 2-digit bcrypt cost in the salt string.""" + mocker.patch('ansible.utils.encrypt.HAS_CRYPT', True) + mocker.patch('ansible._internal._encryption._crypt.CryptFacade.has_crypt_gensalt', False) + + crypt_hash = encrypt.CryptHash("bcrypt") + saltstring = crypt_hash._build_saltstring('2b', 5, '1234567890123456789012', None) + + assert saltstring == '$2b$05$1234567890123456789012' + class TestPasslibHash: """ ++++++ ansible_core-2.21.1.tar.gz.sha256 -> ansible_core-2.21.2.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.21.1.tar.gz.sha256 2026-06-19 17:12:22.795491874 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.1991/ansible_core-2.21.2.tar.gz.sha256 2026-07-14 13:51:54.043041740 +0200 @@ -1 +1 @@ -a5536ece95be84de15212b3644cdbbe9cbd9efd62e4e8a544cd6b0b27a083039 ansible_core-2.21.1.tar.gz +c6139e662eda1bfb5af16029f12afa5edfc7f95f8cd44281b1bdb434a6d9dc70 ansible_core-2.21.2.tar.gz
