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-06-19 16:37:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core (Old) and /work/SRC/openSUSE:Factory/.ansible-core.new.1956 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core" Fri Jun 19 16:37:16 2026 rev:61 rq:1360464 version:2.21.1 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes 2026-06-12 19:27:55.123180618 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.1956/ansible-core.changes 2026-06-19 17:12:22.691488324 +0200 @@ -1,0 +2,34 @@ +Fri Jun 19 05:28:06 UTC 2026 - Johannes Kastl <[email protected]> + +- update to 2.21.1: + * Security Fixes + - ansible-galaxy install - Ensure role requirements are passed + as positional arguments to :command:`git clone`. Previously, + a malicious role author could inject arbitrary git + configuration in role dependencies. (CVE-2026-11332) + - psrp - Do not log raw stdout/stderr on verbosity 5 when task + has no_log: true set + - winrm - Do not log raw stdout/stderr on verbosity 5 when task + has no_log: true set + * Bugfixes + - cli - handle empty value for PAGER (#86898). + - config - use correct key value for inject_invocation setting + (#86999). + - free strategy - Fix IndexError when hosts become unreachable + during playbook execution (#87027). + - meta pseudo-action - Fixed callback args passed to + v2_runner_on_skipped when any meta action was skipped by a + when condition; added test coverage. A previous regression + caused the callback dispatch to be omitted and a warning + issued. + - module_utils sanitize_keys and remove_value functions now + sort their input to ensure matching subsets are always + obscured. + - module_utils/basic.py - Fix AnsibleModule.run_command() to + handle None return from non-blocking pipe reads (#86920). + - wait_for - use errno.ENOENT symbolic constant instead of + hardcoded value for improved code portability. +- remove patch ansible-core-CVE-2026-11332.patch as this is + included in the release + +------------------------------------------------------------------- Old: ---- ansible-core-CVE-2026-11332.patch ansible_core-2.21.0.tar.gz ansible_core-2.21.0.tar.gz.sha256 New: ---- ansible_core-2.21.1.tar.gz ansible_core-2.21.1.tar.gz.sha256 ----------(Old B)---------- Old: hardcoded value for improved code portability. - remove patch ansible-core-CVE-2026-11332.patch as this is included in the release ----------(Old E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core.spec ++++++ --- /var/tmp/diff_new_pack.Ms3d65/_old 2026-06-19 17:12:24.655555367 +0200 +++ /var/tmp/diff_new_pack.Ms3d65/_new 2026-06-19 17:12:24.655555367 +0200 @@ -43,15 +43,13 @@ %endif Name: ansible-core -Version: 2.21.0 +Version: 2.21.1 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later URL: https://ansible.com/ Source0: https://files.pythonhosted.org/packages/source/a/ansible-core/ansible_core-%{version}.tar.gz#/ansible_core-%{version}.tar.gz Source1: ansible_core-%{version}.tar.gz.sha256 -# CVE-2026-11332 (bsc#1267822): argument injection in ansible-galaxy role install -Patch0: ansible-core-CVE-2026-11332.patch BuildArch: noarch # cannot be installed with ansible < 3 or ansible-base ++++++ ansible_core-2.21.0.tar.gz -> ansible_core-2.21.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/PKG-INFO new/ansible_core-2.21.1/PKG-INFO --- old/ansible_core-2.21.0/PKG-INFO 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/PKG-INFO 2026-06-18 21:33:01.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.21.0 +Version: 2.21.1 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.0/ansible_core.egg-info/PKG-INFO new/ansible_core-2.21.1/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.21.0/ansible_core.egg-info/PKG-INFO 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/ansible_core.egg-info/PKG-INFO 2026-06-18 21:33:01.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.21.0 +Version: 2.21.1 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.0/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.21.1/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.21.0/ansible_core.egg-info/SOURCES.txt 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/ansible_core.egg-info/SOURCES.txt 2026-06-18 21:33:01.000000000 +0200 @@ -1048,6 +1048,7 @@ test/integration/targets/ansible-galaxy-role/files/safe-symlinks/tasks/utils/suite.yml test/integration/targets/ansible-galaxy-role/meta/main.yml test/integration/targets/ansible-galaxy-role/tasks/dir-traversal.yml +test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml test/integration/targets/ansible-galaxy-role/tasks/main.yml test/integration/targets/ansible-galaxy-role/tasks/valid-role-symlinks.yml test/integration/targets/ansible-galaxy/files/testserver.py @@ -3411,6 +3412,7 @@ test/integration/targets/no_log/no_log_suboptions_invalid.yml test/integration/targets/no_log/runme.sh test/integration/targets/no_log/secretvars.yml +test/integration/targets/no_log/sub_masking.yml test/integration/targets/no_log/action_plugins/action_sets_no_log.py test/integration/targets/no_log/library/module.py test/integration/targets/noexec/aliases @@ -3982,6 +3984,8 @@ test/integration/targets/strategy-external/runme.sh test/integration/targets/strategy-external/ansible_collections/ns/col/plugins/strategy/external.py test/integration/targets/strategy_free/aliases +test/integration/targets/strategy_free/free_hosts +test/integration/targets/strategy_free/free_index_error.yml test/integration/targets/strategy_free/inventory test/integration/targets/strategy_free/last_include_tasks.yml test/integration/targets/strategy_free/runme.sh diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/changelogs/CHANGELOG-v2.21.rst new/ansible_core-2.21.1/changelogs/CHANGELOG-v2.21.rst --- old/ansible_core-2.21.0/changelogs/CHANGELOG-v2.21.rst 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/changelogs/CHANGELOG-v2.21.rst 2026-06-18 21:33:01.000000000 +0200 @@ -4,6 +4,33 @@ .. contents:: Topics +v2.21.1 +======= + +Release Summary +--------------- + +| Release Date: 2026-06-18 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__ + +Security Fixes +-------------- + +- ansible-galaxy install - Ensure role requirements are passed as positional arguments to :command:`git clone`. Previously, a malicious role author could inject arbitrary git configuration in role dependencies. (CVE-2026-11332) +- psrp - Do not log raw stdout/stderr on verbosity 5 when task has ``no_log: true`` set +- winrm - Do not log raw stdout/stderr on verbosity 5 when task has ``no_log: true`` set + +Bugfixes +-------- + +- cli - handle empty value for PAGER (https://github.com/ansible/ansible/issues/86898). +- config - use correct key value for inject_invocation setting (https://github.com/ansible/ansible/issues/86999). +- free strategy - Fix ``IndexError`` when hosts become unreachable during playbook execution (https://github.com/ansible/ansible/issues/87027). +- meta pseudo-action - Fixed callback args passed to ``v2_runner_on_skipped`` when any ``meta`` action was skipped by a ``when`` condition; added test coverage. A previous regression caused the callback dispatch to be omitted and a warning issued. +- module_utils sanitize_keys and remove_value functions now sort their input to ensure matching subsets are always obscured. +- module_utils/basic.py - Fix ``AnsibleModule.run_command()`` to handle ``None`` return from non-blocking pipe reads (https://github.com/ansible/ansible/issues/86920). +- wait_for - use ``errno.ENOENT`` symbolic constant instead of hardcoded value for improved code portability. + v2.21.0 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/changelogs/changelog.yaml new/ansible_core-2.21.1/changelogs/changelog.yaml --- old/ansible_core-2.21.0/changelogs/changelog.yaml 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/changelogs/changelog.yaml 2026-06-18 21:33:01.000000000 +0200 @@ -638,3 +638,57 @@ - 2.21.0rc1_summary.yaml - 77691-git-track-submodules-branch.yml release_date: '2026-04-28' + 2.21.1: + changes: + release_summary: '| Release Date: 2026-06-18 + + | `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.1_summary.yaml + release_date: '2026-06-18' + 2.21.1rc1: + changes: + bugfixes: + - cli - handle empty value for PAGER (https://github.com/ansible/ansible/issues/86898). + - config - use correct key value for inject_invocation setting (https://github.com/ansible/ansible/issues/86999). + - free strategy - Fix ``IndexError`` when hosts become unreachable during playbook + execution (https://github.com/ansible/ansible/issues/87027). + - meta pseudo-action - Fixed callback args passed to ``v2_runner_on_skipped`` + when any ``meta`` action was skipped by a ``when`` condition; added test coverage. + A previous regression caused the callback dispatch to be omitted and a warning + issued. + - module_utils sanitize_keys and remove_value functions now sort their input + to ensure matching subsets are always obscured. + - module_utils/basic.py - Fix ``AnsibleModule.run_command()`` to handle ``None`` + return from non-blocking pipe reads (https://github.com/ansible/ansible/issues/86920). + - wait_for - use ``errno.ENOENT`` symbolic constant instead of hardcoded value + for improved code portability. + release_summary: '| Release Date: 2026-06-11 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__ + + ' + security_fixes: + - ansible-galaxy install - Ensure role requirements are passed as positional + arguments to :command:`git clone`. Previously, a malicious role author could + inject arbitrary git configuration in role dependencies. (CVE-2026-11332) + - 'psrp - Do not log raw stdout/stderr on verbosity 5 when task has ``no_log: + true`` set' + - 'winrm - Do not log raw stdout/stderr on verbosity 5 when task has ``no_log: + true`` set' + codename: The Rain Song + fragments: + - 2.21.1rc1_summary.yaml + - 86920-fix-run-command-none-read.yml + - 87027-free-strategy-indexerror.yml + - 87087-wait_for-errno-constant.yml + - fix-cloning-malformed-role-requirements.yml + - inject_invocation.yml + - meta_skipped_callback_args.yml + - pager.yml + - sort_obfuscation.yml + - winrm-psrp-nolog.yml + release_date: '2026-06-11' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/cli/__init__.py new/ansible_core-2.21.1/lib/ansible/cli/__init__.py --- old/ansible_core-2.21.0/lib/ansible/cli/__init__.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/cli/__init__.py 2026-06-18 21:33:01.000000000 +0200 @@ -514,17 +514,19 @@ p = subprocess.Popen('less --version', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate() if p.returncode == 0: - CLI.pager_pipe(text, 'less') + CLI.pager_pipe(text, pager='less') else: display.display(text, screen_only=True) @staticmethod - def pager_pipe(text): + def pager_pipe(text, pager=None): """ pipe text through a pager """ - if 'less' in CLI.PAGER: + pager_cmd = pager or CLI.PAGER + + if 'less' in pager_cmd: os.environ['LESS'] = CLI.LESS_OPTS try: - cmd = subprocess.Popen(CLI.PAGER, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout) + cmd = subprocess.Popen(pager_cmd, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout) cmd.communicate(input=to_bytes(text)) except (OSError, KeyboardInterrupt): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/config/base.yml new/ansible_core-2.21.1/lib/ansible/config/base.yml --- old/ansible_core-2.21.0/lib/ansible/config/base.yml 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/config/base.yml 2026-06-18 21:33:01.000000000 +0200 @@ -1677,7 +1677,7 @@ env: - name: ANSIBLE_INJECT_INVOCATION ini: - - key: interpreter_python + - key: inject_invocation section: defaults vars: - name: ansible_inject_invocation diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.21.1/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.21.0/lib/ansible/module_utils/ansible_release.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/module_utils/ansible_release.py 2026-06-18 21:33:01.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.21.0' +__version__ = '2.21.1' __author__ = 'Ansible, Inc.' __codename__ = "The Rain Song" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/module_utils/basic.py new/ansible_core-2.21.1/lib/ansible/module_utils/basic.py --- old/ansible_core-2.21.0/lib/ansible/module_utils/basic.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/module_utils/basic.py 2026-06-18 21:33:01.000000000 +0200 @@ -2092,7 +2092,13 @@ stdout_changed = False for key, event in events: b_chunk = key.fileobj.read(32768) - if not b_chunk and b_chunk is not None: + if b_chunk is None: + # Non-blocking read returned None (no data currently available). + # This can happen with certain file-like objects or in edge cases. + # Skip this chunk and try again on next select iteration. + continue + if not b_chunk: + # Empty bytes received, EOF reached selector.unregister(key.fileobj) elif key.fileobj == cmd.stdout: stdout += b_chunk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/module_utils/common/parameters.py new/ansible_core-2.21.1/lib/ansible/module_utils/common/parameters.py --- old/ansible_core-2.21.0/lib/ansible/module_utils/common/parameters.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/module_utils/common/parameters.py 2026-06-18 21:33:01.000000000 +0200 @@ -500,7 +500,7 @@ return no_log_values -def _sanitize_keys_conditions(value, no_log_strings, ignore_keys, deferred_removals): +def _sanitize_keys_conditions(value, deferred_removals): """ Helper method to :func:`sanitize_keys` to build ``deferred_removals`` and avoid deep recursion. """ if isinstance(value, (str, bytes)): return value @@ -868,8 +868,9 @@ deferred_removals = deque() - no_log_strings = [to_native(s, errors='surrogate_or_strict') for s in no_log_strings] - new_value = _sanitize_keys_conditions(obj, no_log_strings, ignore_keys, deferred_removals) + # sort ensuring we always handle longer strings vs subsets + no_log_strings = sorted([to_native(s, errors='surrogate_or_strict') for s in no_log_strings], key=len, reverse=True) + new_value = _sanitize_keys_conditions(obj, deferred_removals) while deferred_removals: old_data, new_data = deferred_removals.popleft() @@ -877,15 +878,15 @@ if isinstance(new_data, Mapping): for old_key, old_elem in old_data.items(): if old_key in ignore_keys or old_key.startswith('_ansible'): - new_data[old_key] = _sanitize_keys_conditions(old_elem, no_log_strings, ignore_keys, deferred_removals) + new_data[old_key] = _sanitize_keys_conditions(old_elem, deferred_removals) else: # Sanitize the old key. We take advantage of the sanitizing code in # _remove_values_conditions() rather than recreating it here. new_key = _remove_values_conditions(old_key, no_log_strings, None) - new_data[new_key] = _sanitize_keys_conditions(old_elem, no_log_strings, ignore_keys, deferred_removals) + new_data[new_key] = _sanitize_keys_conditions(old_elem, deferred_removals) else: for elem in old_data: - new_elem = _sanitize_keys_conditions(elem, no_log_strings, ignore_keys, deferred_removals) + new_elem = _sanitize_keys_conditions(elem, deferred_removals) if isinstance(new_data, MutableSequence): new_data.append(new_elem) elif isinstance(new_data, MutableSet): @@ -908,7 +909,8 @@ deferred_removals = deque() - no_log_strings = [to_native(s, errors='surrogate_or_strict') for s in no_log_strings] + # sort ensuring we always handle longer strings vs subsets + no_log_strings = sorted([to_native(s, errors='surrogate_or_strict') for s in no_log_strings], key=len, reverse=True) new_value = _remove_values_conditions(value, no_log_strings, deferred_removals) while deferred_removals: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/modules/wait_for.py new/ansible_core-2.21.1/lib/ansible/modules/wait_for.py --- old/ansible_core-2.21.0/lib/ansible/modules/wait_for.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/modules/wait_for.py 2026-06-18 21:33:01.000000000 +0200 @@ -581,7 +581,7 @@ os.stat(b_path) except OSError as e: # If anything except file not present, throw an error - if e.errno != 2: + if e.errno != errno.ENOENT: elapsed = datetime.now(timezone.utc) - start module.fail_json(msg=msg or "Failed to stat %s, %s" % (path, e.strerror), elapsed=elapsed.seconds) # file doesn't exist yet, so continue diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/plugins/connection/psrp.py new/ansible_core-2.21.1/lib/ansible/plugins/connection/psrp.py --- old/ansible_core-2.21.0/lib/ansible/plugins/connection/psrp.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/plugins/connection/psrp.py 2026-06-18 21:33:01.000000000 +0200 @@ -776,9 +776,14 @@ stderr_list += self.host.ui.stderr stderr = "".join([to_text(o) for o in stderr_list]) + log_stdout = stdout + log_stderr = stderr + if self._play_context.no_log: + log_stdout = log_stderr = '<censored due to no log>' + display.vvvvv("PSRP RC: %d" % rc, host=self._psrp_host) - display.vvvvv("PSRP STDOUT: %s" % stdout, host=self._psrp_host) - display.vvvvv("PSRP STDERR: %s" % stderr, host=self._psrp_host) + display.vvvvv(f"PSRP STDOUT: {log_stdout}", host=self._psrp_host) + display.vvvvv(f"PSRP STDERR: {log_stderr}", host=self._psrp_host) # reset the host back output back to defaults, needed if running # multiple pipelines on the same RunspacePool diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/plugins/connection/winrm.py new/ansible_core-2.21.1/lib/ansible/plugins/connection/winrm.py --- old/ansible_core-2.21.0/lib/ansible/plugins/connection/winrm.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/plugins/connection/winrm.py 2026-06-18 21:33:01.000000000 +0200 @@ -634,11 +634,16 @@ stdout = to_text(b_stdout) stderr = to_text(b_stderr) + log_stdout = stdout + log_stderr = stderr + if self._play_context.no_log: + log_stdout = log_stderr = '<censored due to no log>' + if from_exec: - display.vvvvv('WINRM RESULT <Response code %d, out %r, err %r>' % (rc, stdout, stderr), host=self._winrm_host) + display.vvvvv(f'WINRM RESULT <Response code {rc}, out {log_stdout!r}, err {log_stderr!r}>', host=self._winrm_host) display.vvvvvv('WINRM RC %d' % rc, host=self._winrm_host) - display.vvvvvv('WINRM STDOUT %s' % stdout, host=self._winrm_host) - display.vvvvvv('WINRM STDERR %s' % stderr, host=self._winrm_host) + display.vvvvvv(f'WINRM STDOUT {log_stdout}', host=self._winrm_host) + display.vvvvvv(f'WINRM STDERR {log_stderr}', host=self._winrm_host) # This is done after logging so we can still see the raw stderr for # debugging purposes. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/plugins/strategy/__init__.py new/ansible_core-2.21.1/lib/ansible/plugins/strategy/__init__.py --- old/ansible_core-2.21.0/lib/ansible/plugins/strategy/__init__.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/plugins/strategy/__init__.py 2026-06-18 21:33:01.000000000 +0200 @@ -29,7 +29,6 @@ import collections.abc as _c from collections import deque -from sqlite3 import NotSupportedError from ansible import constants as C, constants from ansible import context @@ -685,7 +684,7 @@ for var_name, var_value in variables.items(): self._variable_manager.set_host_variable(target_host, var_name, var_value) case _: - raise NotSupportedError(f"Unsupported variable layer: {variable_layer}") + raise NotImplementedError(f"Unsupported variable layer: {variable_layer}") if result_utr.stats is not None: stats_data = result_utr.stats['data'] @@ -1017,10 +1016,12 @@ else: display.vv(f"META: {header}") + htr = HostTaskResult(host=target_host, task=task, utr=utr) + if skipped: - self._tqm.send_callback('v2_runner_on_skipped', target_host, task, utr) + self._tqm.send_callback('v2_runner_on_skipped', htr) - return [HostTaskResult(host=target_host, task=task, utr=utr)] + return [htr] def _get_cached_role(self, task, play): return play._get_cached_role(task._role) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/plugins/strategy/free.py new/ansible_core-2.21.1/lib/ansible/plugins/strategy/free.py --- old/ansible_core-2.21.0/lib/ansible/plugins/strategy/free.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/plugins/strategy/free.py 2026-06-18 21:33:01.000000000 +0200 @@ -92,6 +92,11 @@ result = False break + # Reset last_host if it's out of bounds for the current hosts_left + # This can happen when hosts become unreachable between iterations + if last_host >= len(hosts_left): + last_host = 0 + work_to_do = False # assume we have no more work to do starting_host = last_host # save current position so we know when we've looped back around and need to break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/release.py new/ansible_core-2.21.1/lib/ansible/release.py --- old/ansible_core-2.21.0/lib/ansible/release.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/release.py 2026-06-18 21:33:01.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.21.0' +__version__ = '2.21.1' __author__ = 'Ansible, Inc.' __codename__ = "The Rain Song" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/lib/ansible/utils/galaxy.py new/ansible_core-2.21.1/lib/ansible/utils/galaxy.py --- old/ansible_core-2.21.0/lib/ansible/utils/galaxy.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/lib/ansible/utils/galaxy.py 2026-06-18 21:33:01.000000000 +0200 @@ -72,7 +72,7 @@ elif scm == 'hg': clone_cmd.append('--insecure') - clone_cmd.extend([src, name]) + clone_cmd.extend(['--', src, name]) run_scm_cmd(clone_cmd, tempdir) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/ansible-doc/runme.sh new/ansible_core-2.21.1/test/integration/targets/ansible-doc/runme.sh --- old/ansible_core-2.21.0/test/integration/targets/ansible-doc/runme.sh 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/ansible-doc/runme.sh 2026-06-18 21:33:01.000000000 +0200 @@ -59,6 +59,16 @@ expected_out="$(sed '1 s/\(^> TEST testns\.testcol\.yolo\).*(.*)$/\1/' yolo-text.output)" test "$current_out" == "$expected_out" +# PAGER is set to less by default, so we need to test with other pagers +for pager in more cat; do + echo "testing with pager $pager" + PAGER=$pager ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep "${GREP_OPTS[@]}" -v "Invalid collection name" +done + +# set pager to empty string +echo "testing with pager empty" +PAGER="" ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep "${GREP_OPTS[@]}" -v "Invalid collection name" + # ensure we do work with valid collection name for list ansible-doc --list testns.testcol --playbook-dir ./ 2>&1 | grep -v "Invalid collection name" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml new/ansible_core-2.21.1/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml --- old/ansible_core-2.21.0/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.1/test/integration/targets/ansible-galaxy-role/tasks/git-config-injection.yml 2026-06-18 21:33:01.000000000 +0200 @@ -0,0 +1,52 @@ +- vars: + invalid_git_opts: '-ccore.sshCommand=sh -c "id > {{ remote_tmp_dir }}/role_exe"' + # use SSH protocol to test core.sshCommand is not configured + dummy_repo: [email protected]:ansible/nosuchrepo.git + block: + - name: Ensure git is installed + package: + name: git + when: ansible_distribution not in ["MacOSX", "Alpine"] + register: git_install + + - name: Create invalid requirements file + copy: + dest: "{{ remote_tmp_dir }}/invalid-requirements.yml" + content: | + - src: {{ invalid_git_opts }} + scm: git + name: {{ dummy_repo }} + - src: {{ dummy_repo }} + scm: git + name: {{ invalid_git_opts }} + + - name: Attempt to install invalid role requirements + command: ansible-galaxy install -r {{ remote_tmp_dir }}/invalid-requirements.yml --ignore-errors + register: result + environment: + ANSIBLE_NOCOLOR: True + ANSIBLE_FORCE_COLOR: False + + - name: Validate git core.sshCommand did not run + stat: + path: "{{ remote_tmp_dir }}/role_exe" + register: stat_result + failed_when: stat_result.stat.exists + + - name: Verify the invalid field is treated as a single positional argument (repo or dest) + assert: + that: + - stderr is search(error1) + - stderr is search(error2) + - (stderr | regex_findall("git clone") | length) == (stderr | regex_findall("git clone --") | length) == 2 + vars: + stderr: "{{ result.stderr | regex_replace('\\n', ' ') }}" + error1: "repository '{{ invalid_git_opts }}' does not exist" + error2: "Cloning into '{{ invalid_git_opts }}'" + + always: + - name: Uninstall git if it was installed + package: + name: git + state: absent + when: git_install is changed | default(false) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/ansible-galaxy-role/tasks/main.yml new/ansible_core-2.21.1/test/integration/targets/ansible-galaxy-role/tasks/main.yml --- old/ansible_core-2.21.0/test/integration/targets/ansible-galaxy-role/tasks/main.yml 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/ansible-galaxy-role/tasks/main.yml 2026-06-18 21:33:01.000000000 +0200 @@ -70,3 +70,4 @@ - import_tasks: dir-traversal.yml - import_tasks: valid-role-symlinks.yml +- import_tasks: git-config-injection.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/meta_tasks/runme.sh new/ansible_core-2.21.1/test/integration/targets/meta_tasks/runme.sh --- old/ansible_core-2.21.0/test/integration/targets/meta_tasks/runme.sh 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/meta_tasks/runme.sh 2026-06-18 21:33:01.000000000 +0200 @@ -40,8 +40,10 @@ # test end_play meta task for test_strategy in linear free; do - out="$(ansible-playbook test_end_play.yml -i inventory.yml -e test_strategy=$test_strategy -vv "$@")" + # $@ omitted to avoid additional verbosity, which will line-break and indent the JSON output we need to check + out="$(ansible-playbook test_end_play.yml -i inventory.yml -e test_strategy=$test_strategy -vv)" + grep -q "skipping:.*end_play conditional evaluated to False, continuing play" <<< "$out" grep -q "META: ending play" <<< "$out" grep -qv 'Failed to end using end_play' <<< "$out" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/meta_tasks/test_end_play.yml new/ansible_core-2.21.1/test/integration/targets/meta_tasks/test_end_play.yml --- old/ansible_core-2.21.0/test/integration/targets/meta_tasks/test_end_play.yml 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/meta_tasks/test_end_play.yml 2026-06-18 21:33:01.000000000 +0200 @@ -3,6 +3,10 @@ gather_facts: no strategy: "{{ test_strategy | default('linear') }}" tasks: + - name: Skipped end_play + meta: end_play + when: false + - debug: msg: "Testing end_play on host {{ inventory_hostname }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/no_log/library/module.py new/ansible_core-2.21.1/test/integration/targets/no_log/library/module.py --- old/ansible_core-2.21.0/test/integration/targets/no_log/library/module.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/no_log/library/module.py 2026-06-18 21:33:01.000000000 +0200 @@ -37,7 +37,7 @@ } ) - module.exit_json(msg='done') + module.exit_json(msg='done', values=', '.join([str(v) for v in module.params.values() if v])) if __name__ == '__main__': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/no_log/runme.sh new/ansible_core-2.21.1/test/integration/targets/no_log/runme.sh --- old/ansible_core-2.21.0/test/integration/targets/no_log/runme.sh 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/no_log/runme.sh 2026-06-18 21:33:01.000000000 +0200 @@ -33,3 +33,6 @@ [ "$(ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ] [ "$(ANSIBLE_NO_LOG=0 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "1" ] [ "$(ANSIBLE_NO_LOG=1 ansible-playbook no_log_config.yml -i ../../inventory -vvvvv "$@" | grep -Ec 'the output has been hidden')" = "5" ] + +# Ensure module no_log masking handles secrets that are substrings of other secrets correctly +ansible-playbook sub_masking.yml -i ../../inventory -vvvvv "$@" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/no_log/secretvars.yml new/ansible_core-2.21.1/test/integration/targets/no_log/secretvars.yml --- old/ansible_core-2.21.0/test/integration/targets/no_log/secretvars.yml 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/no_log/secretvars.yml 2026-06-18 21:33:01.000000000 +0200 @@ -30,3 +30,6 @@ s211: SECRET211 s212: SECRET212 s213: SECRET213 + +# substring +sec: RET diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/no_log/sub_masking.yml new/ansible_core-2.21.1/test/integration/targets/no_log/sub_masking.yml --- old/ansible_core-2.21.0/test/integration/targets/no_log/sub_masking.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.1/test/integration/targets/no_log/sub_masking.yml 2026-06-18 21:33:01.000000000 +0200 @@ -0,0 +1,60 @@ +- name: Ensure no_log obfuscation does not mask on substrings + hosts: all + vars_files: + - secretvars.yml + gather_facts: no + + tasks: + - name: test with option long + module: + secret: "{{ s101 }}" + subopt_dict: + str_sub_opt1: "{{ s102 }}" + nested_subopt: + n_subopt1: "{{ s103 }}" + subopt_list: + - subopt1: "{{sec}}" + register: long_first + + - name: Task with suboptions long + module: + secret: "{{ sec }}" + subopt_dict: + str_sub_opt1: '{{ s101 }}' + nested_subopt: + n_subopt1: "{{ s102 }}" + subopt_list: + - subopt1: "{{ s103 }}" + register: short_first + + - name: Task with suboptions long + module: + secret: "{{ s101 }}" + subopt_dict: + str_sub_opt1: '{{ sec }}' + nested_subopt: + n_subopt1: "{{ s102 }}" + subopt_list: + - subopt1: "{{ s103 }}" + register: middle_top + + - name: Task with suboptions long + module: + secret: "{{ s102}}" + subopt_dict: + str_sub_opt1: '{{ s102 }}' + nested_subopt: + n_subopt1: "{{ sec }}" + subopt_list: + - subopt1: "{{ s103 }}" + register: middle_bottom + + - name: check output + assert: + that: + - "'SEC' not in (q('vars', item)|to_json)" + loop: + - long_first + - short_first + - middle_top + - middle_bottom diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/strategy_free/free_hosts new/ansible_core-2.21.1/test/integration/targets/strategy_free/free_hosts --- old/ansible_core-2.21.0/test/integration/targets/strategy_free/free_hosts 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.1/test/integration/targets/strategy_free/free_hosts 2026-06-18 21:33:01.000000000 +0200 @@ -0,0 +1,4 @@ +[local] +host0 ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}" +host1 ansible_connection=ssh ansible_host=127.0.0.1 ansible_port=1011 # IANA Reserved port +host2 ansible_connection=local ansible_python_interpreter="{{ ansible_playbook_python }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/strategy_free/free_index_error.yml new/ansible_core-2.21.1/test/integration/targets/strategy_free/free_index_error.yml --- old/ansible_core-2.21.0/test/integration/targets/strategy_free/free_index_error.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.21.1/test/integration/targets/strategy_free/free_index_error.yml 2026-06-18 21:33:01.000000000 +0200 @@ -0,0 +1,11 @@ +--- +- hosts: host0, host1, host2 + strategy: free + gather_facts: false + tasks: + - name: EXPECTED FAILURE - First ping + ping: + throttle: 2 + + - name: Second ping + ping: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/integration/targets/strategy_free/runme.sh new/ansible_core-2.21.1/test/integration/targets/strategy_free/runme.sh --- old/ansible_core-2.21.0/test/integration/targets/strategy_free/runme.sh 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/integration/targets/strategy_free/runme.sh 2026-06-18 21:33:01.000000000 +0200 @@ -8,3 +8,9 @@ result="$(ansible-playbook test_last_include_in_always.yml -i inventory "$@" 2>&1)" set -e grep -q "INCLUDED TASK EXECUTED" <<< "$result" + +set +e +result="$(ansible-playbook free_index_error.yml -i free_hosts "$@" 2>&1)" +set -e +grep -q "\[host1\]: UNREACHABLE!" <<< "$result" +! grep -q "IndexError: list index out of range" <<< "$result" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/lib/ansible_test/_util/target/setup/bootstrap.sh new/ansible_core-2.21.1/test/lib/ansible_test/_util/target/setup/bootstrap.sh --- old/ansible_core-2.21.0/test/lib/ansible_test/_util/target/setup/bootstrap.sh 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/lib/ansible_test/_util/target/setup/bootstrap.sh 2026-06-18 21:33:01.000000000 +0200 @@ -401,9 +401,13 @@ # Instead, ansible-test will install it using pip. # packaging and resolvelib are missing for controller supported Python versions, so we just # skip them and let ansible-test install them from PyPI. + # + # sqlite-libs needs to be specified currently to get sqlite3 imports working + # https://redhat.atlassian.net/browse/RHEL-178008 if [ "${controller}" ]; then packages=" ${packages} + sqlite-libs ${py_pkg_prefix}-cryptography ${py_pkg_prefix}-pyyaml " diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.21.0/test/units/module_utils/basic/test_run_command.py new/ansible_core-2.21.1/test/units/module_utils/basic/test_run_command.py --- old/ansible_core-2.21.0/test/units/module_utils/basic/test_run_command.py 2026-05-18 21:18:02.000000000 +0200 +++ new/ansible_core-2.21.1/test/units/module_utils/basic/test_run_command.py 2026-06-18 21:33:01.000000000 +0200 @@ -255,3 +255,81 @@ assert subprocess_mock.Popen.call_args[1]['pass_fds'] == (101, 42) assert subprocess_mock.Popen.call_args[1]['close_fds'] is True + + +class TestRunCommandNoneRead: + """ + Test handling of read() returning None from non-blocking pipes. + + This tests the fix for issue #86920 where read() can return None + in certain edge cases with non-blocking I/O, which would cause + TypeError when trying to concatenate None to bytes. + """ + + class NoneReturningBytesIO(SpecialBytesIO): + """ + BytesIO that returns None on first read, then actual data. + + This simulates edge cases where non-blocking read() returns None + to indicate "no data available right now" rather than empty bytes. + """ + + def __init__(self, *args, **kwargs): + # Pop 'data' before calling super().__init__() since BytesIO doesn't accept it + self.data = kwargs.pop('data', b'test output') + self.read_count = 0 + super(TestRunCommandNoneRead.NoneReturningBytesIO, self).__init__(*args, **kwargs) + + def read(self, size=-1): + self.read_count += 1 + if self.read_count == 1: + # First read returns None (no data available) + return None + elif self.read_count == 2: + # Second read returns actual data + return self.data + else: + # Subsequent reads return empty bytes (EOF) + return b'' + + @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) + def test_none_from_stdout_read(self, mocker, rc_am): + """Test that None returned from stdout.read() doesn't cause TypeError.""" + rc_am._subprocess._output = { + mocker.sentinel.stdout: + self.NoneReturningBytesIO(fh=mocker.sentinel.stdout, data=b'command output'), + mocker.sentinel.stderr: + SpecialBytesIO(b'', fh=mocker.sentinel.stderr) + } + (rc, stdout, stderr) = rc_am.run_command('/bin/test') + assert rc == 0 + assert stdout == 'command output' + assert stderr == '' + + @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) + def test_none_from_stderr_read(self, mocker, rc_am): + """Test that None returned from stderr.read() doesn't cause TypeError.""" + rc_am._subprocess._output = { + mocker.sentinel.stdout: + SpecialBytesIO(b'', fh=mocker.sentinel.stdout), + mocker.sentinel.stderr: + self.NoneReturningBytesIO(fh=mocker.sentinel.stderr, data=b'error output') + } + (rc, stdout, stderr) = rc_am.run_command('/bin/test') + assert rc == 0 + assert stdout == '' + assert stderr == 'error output' + + @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) + def test_none_from_both_pipes(self, mocker, rc_am): + """Test that None returned from both pipes doesn't cause TypeError.""" + rc_am._subprocess._output = { + mocker.sentinel.stdout: + self.NoneReturningBytesIO(fh=mocker.sentinel.stdout, data=b'stdout data'), + mocker.sentinel.stderr: + self.NoneReturningBytesIO(fh=mocker.sentinel.stderr, data=b'stderr data') + } + (rc, stdout, stderr) = rc_am.run_command('/bin/test') + assert rc == 0 + assert stdout == 'stdout data' + assert stderr == 'stderr data' ++++++ ansible_core-2.21.0.tar.gz.sha256 -> ansible_core-2.21.1.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.21.0.tar.gz.sha256 2026-06-03 20:27:21.449494128 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.1956/ansible_core-2.21.1.tar.gz.sha256 2026-06-19 17:12:22.795491874 +0200 @@ -1 +1 @@ -28ccd0e2d1849f1c7272cec39a74a8a5c83f3d51314658fa5ca57ea85a87f454 ansible_core-2.21.0.tar.gz +a5536ece95be84de15212b3644cdbbe9cbd9efd62e4e8a544cd6b0b27a083039 ansible_core-2.21.1.tar.gz
