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-08-11 17:17:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ansible-core (Old)
 and      /work/SRC/openSUSE:Factory/.ansible-core.new.17972 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ansible-core"

Tue Aug 11 17:17:01 2026 rev:63 rq:1370641 version:2.21.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes        
2026-07-14 13:51:53.823034133 +0200
+++ /work/SRC/openSUSE:Factory/.ansible-core.new.17972/ansible-core.changes     
2026-08-11 17:18:09.832660159 +0200
@@ -1,0 +2,26 @@
+Tue Aug 11 07:25:48 UTC 2026 - Johannes Kastl 
<[email protected]>
+
+- update to 2.21.3:
+  
https://github.com/ansible/ansible/blob/v2.21.3/changelogs/CHANGELOG-v2.21.rst
+  * Bugfixes
+    - Add deprecation status to the tree and oneline callback
+      DOCUMENTATION. (#87020)
+    - ansible-galaxy - Fix attempting to download the collection
+      again if the response from the server is shorter than
+      expected, instead of failing due to the mismatched artifact
+      hash on the first attempt. (#86025)
+    - ansible-test - Fix target filtering to preserve
+      user-specified versions that are not in the completion
+      configuration.
+    - collection loader - Fix the collection loader logic to
+      correctly return Python module when calling
+      pkgutil.iter_modules with a package that is inside a
+      collection path and contains compiled Python extension
+      modules.
+    - powershell exec_wrapper - fix handling when multiple pwsh
+      executables match by selecting the first result (#87228).
+    - rpm_key - ensure a trailing newline is present on PGP armor
+      data before passing it to librpm for parsing, fixing failures
+      on systems where pgpParsePkts requires it (#87303).
+
+-------------------------------------------------------------------

Old:
----
  ansible_core-2.21.2.tar.gz
  ansible_core-2.21.2.tar.gz.sha256

New:
----
  ansible_core-2.21.3.tar.gz
  ansible_core-2.21.3.tar.gz.sha256

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ansible-core.spec ++++++
--- /var/tmp/diff_new_pack.81Mxfm/_old  2026-08-11 17:18:13.076797631 +0200
+++ /var/tmp/diff_new_pack.81Mxfm/_new  2026-08-11 17:18:13.080797800 +0200
@@ -43,7 +43,7 @@
 %endif
 
 Name:           ansible-core
-Version:        2.21.2
+Version:        2.21.3
 Release:        0
 Summary:        Radically simple IT automation
 License:        GPL-3.0-or-later

++++++ ansible_core-2.21.2.tar.gz -> ansible_core-2.21.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/PKG-INFO 
new/ansible_core-2.21.3/PKG-INFO
--- old/ansible_core-2.21.2/PKG-INFO    2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/PKG-INFO    2026-08-10 17:52:54.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: ansible-core
-Version: 2.21.2
+Version: 2.21.3
 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.2/ansible_core.egg-info/PKG-INFO 
new/ansible_core-2.21.3/ansible_core.egg-info/PKG-INFO
--- old/ansible_core-2.21.2/ansible_core.egg-info/PKG-INFO      2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/ansible_core.egg-info/PKG-INFO      2026-08-10 
17:52:54.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: ansible-core
-Version: 2.21.2
+Version: 2.21.3
 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.2/ansible_core.egg-info/SOURCES.txt 
new/ansible_core-2.21.3/ansible_core.egg-info/SOURCES.txt
--- old/ansible_core-2.21.2/ansible_core.egg-info/SOURCES.txt   2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/ansible_core.egg-info/SOURCES.txt   2026-08-10 
17:52:54.000000000 +0200
@@ -1763,6 +1763,12 @@
 test/integration/targets/collection/aliases
 test/integration/targets/collection/setup.sh
 test/integration/targets/collection/update-ignore.py
+test/integration/targets/collection_loader/aliases
+test/integration/targets/collection_loader/main.yml
+test/integration/targets/collection_loader/runme.sh
+test/integration/targets/collection_loader/ansible_collections/ns/col/plugins/action/test_collection_loader.py
+test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/__init__.py
+test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/sub/__init__.py
 test/integration/targets/collections/a.statichost.yml
 test/integration/targets/collections/aliases
 test/integration/targets/collections/cache.statichost.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/changelogs/CHANGELOG-v2.21.rst 
new/ansible_core-2.21.3/changelogs/CHANGELOG-v2.21.rst
--- old/ansible_core-2.21.2/changelogs/CHANGELOG-v2.21.rst      2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/changelogs/CHANGELOG-v2.21.rst      2026-08-10 
17:52:54.000000000 +0200
@@ -4,6 +4,25 @@
 
 .. contents:: Topics
 
+v2.21.3
+=======
+
+Release Summary
+---------------
+
+| Release Date: 2026-08-10
+| `Porting Guide 
<https://docs.ansible.com/ansible-core/2.21/porting_guides/porting_guide_core_2.21.html>`__
+
+Bugfixes
+--------
+
+- Add deprecation status to the tree and oneline callback DOCUMENTATION. 
(https://github.com/ansible/ansible/issues/87020)
+- ansible-galaxy - Fix attempting to download the collection again if the 
response from the server is shorter than expected, instead of failing due to 
the mismatched artifact hash on the first attempt. 
(https://github.com/ansible/ansible/pull/86025)
+- ansible-test - Fix target filtering to preserve user-specified versions that 
are not in the completion configuration.
+- collection loader - Fix the collection loader logic to correctly return 
Python module when calling ``pkgutil.iter_modules`` with a package that is 
inside a collection path and contains compiled Python extension modules.
+- powershell exec_wrapper - fix handling when multiple pwsh executables match 
by selecting the first result (https://github.com/ansible/ansible/issues/87228).
+- rpm_key - ensure a trailing newline is present on PGP armor data before 
passing it to librpm for parsing, fixing failures on systems where 
``pgpParsePkts`` requires it (https://github.com/ansible/ansible/issues/87303).
+
 v2.21.2
 =======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/changelogs/changelog.yaml 
new/ansible_core-2.21.3/changelogs/changelog.yaml
--- old/ansible_core-2.21.2/changelogs/changelog.yaml   2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/changelogs/changelog.yaml   2026-08-10 
17:52:54.000000000 +0200
@@ -727,3 +727,46 @@
     - ansible-test-timeout-callback.yml
     - fix-short-timeout-async-wrapper.yml
     release_date: '2026-07-06'
+  2.21.3:
+    changes:
+      release_summary: '| Release Date: 2026-08-10
+
+        | `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.3_summary.yaml
+    release_date: '2026-08-10'
+  2.21.3rc1:
+    changes:
+      bugfixes:
+      - Add deprecation status to the tree and oneline callback DOCUMENTATION. 
(https://github.com/ansible/ansible/issues/87020)
+      - ansible-galaxy - Fix attempting to download the collection again if 
the response
+        from the server is shorter than expected, instead of failing due to 
the mismatched
+        artifact hash on the first attempt. 
(https://github.com/ansible/ansible/pull/86025)
+      - ansible-test - Fix target filtering to preserve user-specified 
versions that
+        are not in the completion configuration.
+      - collection loader - Fix the collection loader logic to correctly 
return Python
+        module when calling ``pkgutil.iter_modules`` with a package that is 
inside
+        a collection path and contains compiled Python extension modules.
+      - powershell exec_wrapper - fix handling when multiple pwsh executables 
match
+        by selecting the first result 
(https://github.com/ansible/ansible/issues/87228).
+      - rpm_key - ensure a trailing newline is present on PGP armor data 
before passing
+        it to librpm for parsing, fixing failures on systems where 
``pgpParsePkts``
+        requires it (https://github.com/ansible/ansible/issues/87303).
+      release_summary: '| Release Date: 2026-08-03
+
+        | `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.3rc1_summary.yaml
+    - 87304-rpm_key-armor-trailing-newline.yml
+    - ansible-galaxy-retry-IncompleteRead-download.yml
+    - ansible-test-target-filtering.yml
+    - collection-loader-extensions.yml
+    - fix-tree-and-oneline-callback-deprecation.yml
+    - powershell-exec-wrapper-select-first.yml
+    release_date: '2026-08-03'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/lib/ansible/cli/galaxy.py 
new/ansible_core-2.21.3/lib/ansible/cli/galaxy.py
--- old/ansible_core-2.21.2/lib/ansible/cli/galaxy.py   2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/cli/galaxy.py   2026-08-10 
17:52:54.000000000 +0200
@@ -203,7 +203,7 @@
             if args[1:3] == ['role', 'login']:
                 display.error(
                     "The login command was removed in late 2020. An API key is 
now required to publish roles or collections "
-                    "to Galaxy. The key can be found at 
https://galaxy.ansible.com/me/preferences, and passed to the "
+                    "to Galaxy. The key can be found at 
https://galaxy.ansible.com/ui/token, and passed to the "
                     "ansible-galaxy CLI via a file at {0} or (insecurely) via 
the `--token` "
                     "command-line 
argument.".format(to_text(C.GALAXY_TOKEN_PATH)))
                 sys.exit(1)
@@ -238,7 +238,7 @@
         common.add_argument('--api-version', type=int, choices=[2, 3], 
help=argparse.SUPPRESS)  # Hidden argument that should only be used in our tests
         common.add_argument('--token', '--api-key', dest='api_key',
                             help='The Ansible Galaxy API key which can be 
found at '
-                                 'https://galaxy.ansible.com/me/preferences.')
+                                 'https://galaxy.ansible.com/ui/token.')
         common.add_argument('-c', '--ignore-certs', action='store_true', 
dest='ignore_certs', help='Ignore SSL certificate validation errors.', 
default=None)
 
         # --timeout uses the default None to handle two different scenarios.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/config/ansible_builtin_runtime.yml 
new/ansible_core-2.21.3/lib/ansible/config/ansible_builtin_runtime.yml
--- old/ansible_core-2.21.2/lib/ansible/config/ansible_builtin_runtime.yml      
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/config/ansible_builtin_runtime.yml      
2026-08-10 17:52:54.000000000 +0200
@@ -9140,6 +9140,14 @@
     mongodb:
       redirect: community.mongodb.mongodb
   callback:
+    tree:
+      deprecation:
+        removal_version: "2.23"
+        warning_text: "Use another callback plugin, or vendor and/or move the 
tree callback to a collection."
+    oneline:
+      deprecation:
+        removal_version: "2.23"
+        warning_text: "Use another callback plugin, or vendor and/or move the 
oneline callback to a collection."
     actionable:
       redirect: community.general.actionable
     cgroup_memory_recap:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/executor/powershell/exec_wrapper.ps1 
new/ansible_core-2.21.3/lib/ansible/executor/powershell/exec_wrapper.ps1
--- old/ansible_core-2.21.2/lib/ansible/executor/powershell/exec_wrapper.ps1    
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/executor/powershell/exec_wrapper.ps1    
2026-08-10 17:52:54.000000000 +0200
@@ -82,6 +82,7 @@
         $null = $PSBoundParameters.Remove('PwshPath')
 
         $targetPwsh = Get-Command -Name $PwshPath -CommandType Application 
-ErrorAction Ignore |
+            Select-Object -First 1 |
             ForEach-Object { [Path]::GetFullPath($_.Path) }
         if (-not $targetPwsh) {
             @{
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/galaxy/collection/concrete_artifact_manager.py
 
new/ansible_core-2.21.3/lib/ansible/galaxy/collection/concrete_artifact_manager.py
--- 
old/ansible_core-2.21.2/lib/ansible/galaxy/collection/concrete_artifact_manager.py
  2026-07-13 21:37:14.000000000 +0200
+++ 
new/ansible_core-2.21.3/lib/ansible/galaxy/collection/concrete_artifact_manager.py
  2026-08-10 17:52:54.000000000 +0200
@@ -17,6 +17,7 @@
 from functools import cache
 from hashlib import sha256
 from pathlib import Path
+from http.client import IncompleteRead, HTTPResponse
 from urllib.error import URLError
 from urllib.parse import urldefrag
 from shutil import rmtree
@@ -509,7 +510,10 @@
     )
 
     with open(b_file_path, 'wb') as download_file:  # type: t.BinaryIO
-        actual_hash = _consume_file(resp, write_to=download_file)
+        try:
+            actual_hash = _consume_file(resp, write_to=download_file)
+        except IncompleteRead as orig_exc:
+            raise AnsibleError(f"Downloading {url} failed") from orig_exc
 
     if expected_hash:
         display.vvvv(
@@ -523,17 +527,29 @@
     return b_file_path
 
 
-def _consume_file(read_from, write_to=None):
-    # type: (t.BinaryIO, t.BinaryIO) -> str
+def _consume_file(read_from: t.BinaryIO | HTTPResponse, write_to: t.BinaryIO | 
None = None) -> str:
     bufsize = 65536
     sha256_digest = sha256()
-    data = read_from.read(bufsize)
-    while data:
+    length = getattr(read_from, 'length', None)
+    actual_length = 0
+    remaining = None
+    while True:
+        if isinstance(length, int) and (remaining := length - actual_length) 
<= 0:
+            break
+
+        if not (data := read_from.read(min(bufsize, remaining or bufsize))):
+            break
+
         if write_to is not None:
             write_to.write(data)
             write_to.flush()
+
         sha256_digest.update(data)
-        data = read_from.read(bufsize)
+        actual_length += len(data)
+
+    if isinstance(length, int) and length > actual_length:
+        read_len = b' ' * actual_length if actual_length else b''
+        raise IncompleteRead(read_len, length - actual_length)
 
     return sha256_digest.hexdigest()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/module_utils/ansible_release.py 
new/ansible_core-2.21.3/lib/ansible/module_utils/ansible_release.py
--- old/ansible_core-2.21.2/lib/ansible/module_utils/ansible_release.py 
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/module_utils/ansible_release.py 
2026-08-10 17:52:54.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.21.2'
+__version__ = '2.21.3'
 __author__ = 'Ansible, Inc.'
 __codename__ = "The Rain Song"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/lib/ansible/modules/rpm_key.py 
new/ansible_core-2.21.3/lib/ansible/modules/rpm_key.py
--- old/ansible_core-2.21.2/lib/ansible/modules/rpm_key.py      2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/modules/rpm_key.py      2026-08-10 
17:52:54.000000000 +0200
@@ -161,6 +161,9 @@
         pkt = ctypes.POINTER(ctypes.c_uint8)()
         pktlen = ctypes.c_size_t()
 
+        if not armor.endswith("\n"):
+            armor += "\n"
+
         armor_bytes = armor.encode()
         result = self._lib.pgpParsePkts(armor_bytes, ctypes.byref(pkt), 
ctypes.byref(pktlen))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/plugins/callback/oneline.py 
new/ansible_core-2.21.3/lib/ansible/plugins/callback/oneline.py
--- old/ansible_core-2.21.2/lib/ansible/plugins/callback/oneline.py     
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/plugins/callback/oneline.py     
2026-08-10 17:52:54.000000000 +0200
@@ -11,13 +11,16 @@
     version_added: historical
     description:
         - This is the output callback used by the C(-o)/C(--one-line) command 
line option.
+    deprecated:
+      why: The P(ansible.builtin.oneline#callback) callback is no longer 
recommended due to long term instability.
+      alternatives: Use a stable callback with test coverage, or consider 
vendoring and/or adding this plugin to a collection.
+      removed_in: "2.23"
 """
 
 from ansible import constants as C
 from ansible.plugins.callback import CallbackBase
 from ansible.template import Templar
 from ansible.executor.task_result import CallbackTaskResult
-from ansible.module_utils._internal import _deprecator
 
 
 class CallbackModule(CallbackBase):
@@ -31,15 +34,6 @@
     CALLBACK_TYPE = 'stdout'
     CALLBACK_NAME = 'oneline'
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        self._display.deprecated(  # pylint: 
disable=ansible-deprecated-unnecessary-collection-name
-            msg='The oneline callback plugin is deprecated.',
-            version='2.23',
-            deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,  # entire plugin 
being removed; this improves the messaging
-        )
-
     def _command_generic_msg(self, hostname, result, caption):
         stdout = result.get('stdout', '').replace('\n', '\\n').replace('\r', 
'\\r')
         if 'stderr' in result and result['stderr']:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/plugins/callback/tree.py 
new/ansible_core-2.21.3/lib/ansible/plugins/callback/tree.py
--- old/ansible_core-2.21.2/lib/ansible/plugins/callback/tree.py        
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/plugins/callback/tree.py        
2026-08-10 17:52:54.000000000 +0200
@@ -25,6 +25,10 @@
     description:
         - "This callback is used by the Ansible (adhoc) command line option 
C(-t|--tree)."
         - This produces a JSON dump of events in a directory, a file for each 
host, the directory used MUST be passed as a command line option.
+    deprecated:
+      why: The P(ansible.builtin.tree#callback) callback is no longer 
recommended due to long term instability.
+      alternatives: Use a stable callback with test coverage, or consider 
vendoring and/or moving this plugin to a collection.
+      removed_in: "2.23"
 """
 
 import os
@@ -34,7 +38,6 @@
 from ansible.module_utils.common.text.converters import to_bytes
 from ansible.plugins.callback import CallbackBase
 from ansible.utils.path import makedirs_safe, unfrackpath
-from ansible.module_utils._internal import _deprecator
 
 
 class CallbackModule(CallbackBase):
@@ -47,15 +50,6 @@
     CALLBACK_NAME = 'tree'
     CALLBACK_NEEDS_ENABLED = True
 
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-
-        self._display.deprecated(  # pylint: 
disable=ansible-deprecated-unnecessary-collection-name
-            msg='The tree callback plugin is deprecated.',
-            version='2.23',
-            deprecator=_deprecator.ANSIBLE_CORE_DEPRECATOR,  # entire plugin 
being removed; this improves the messaging
-        )
-
     def set_options(self, task_keys=None, var_options=None, direct=None):
         """ override to set self.tree """
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/lib/ansible/release.py 
new/ansible_core-2.21.3/lib/ansible/release.py
--- old/ansible_core-2.21.2/lib/ansible/release.py      2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/lib/ansible/release.py      2026-08-10 
17:52:54.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.21.2'
+__version__ = '2.21.3'
 __author__ = 'Ansible, Inc.'
 __codename__ = "The Rain Song"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/lib/ansible/utils/collection_loader/_collection_finder.py
 
new/ansible_core-2.21.3/lib/ansible/utils/collection_loader/_collection_finder.py
--- 
old/ansible_core-2.21.2/lib/ansible/utils/collection_loader/_collection_finder.py
   2026-07-13 21:37:14.000000000 +0200
+++ 
new/ansible_core-2.21.3/lib/ansible/utils/collection_loader/_collection_finder.py
   2026-08-10 17:52:54.000000000 +0200
@@ -6,6 +6,7 @@
 
 from __future__ import annotations
 
+import inspect
 import itertools
 import os
 import os.path
@@ -1237,23 +1238,33 @@
         prefix = _to_text(prefix)
     # yield (module_loader, name, ispkg) for each module/pkg under path
     # TODO: implement ignore/silent catch for unreadable?
-    for b_path in map(_to_bytes, paths):
-        if not os.path.isdir(b_path):
+    # Mostly based off the logic in the builtin pkgutil.iter_modules importer.
+    # Only difference is we don't check if dirs contains __init__.
+    # 
https://github.com/python/cpython/blob/fda056e64bdfcac3dd3d13eebda0a24994d83cb8/Lib/pkgutil.py#L130-L168
+    for path in paths:
+        if not os.path.isdir(path):
             continue
-        for b_basename in sorted(os.listdir(b_path)):
-            b_candidate_module_path = os.path.join(b_path, b_basename)
-            if os.path.isdir(b_candidate_module_path):
+
+        yielded = {'__init__'}
+        for basename in sorted(os.listdir(path)):
+            modname = inspect.getmodulename(basename)
+            if modname in yielded:
+                continue
+
+            mod_path = os.path.join(path, basename)
+            ispkg = False
+            if not modname and os.path.isdir(mod_path) and '.' not in basename:
                 # exclude things that obviously aren't Python package dirs
                 # FIXME: this dir is adjustable in py3.8+, check for it
-                if b'.' in b_basename or b_basename == b'__pycache__':
+                if basename == '__pycache__':
                     continue
 
-                # TODO: proper string handling?
-                yield prefix + _to_text(b_basename), True
-            else:
-                # FIXME: match builtin ordering for package/dir/file, support 
compiled?
-                if b_basename.endswith(b'.py') and b_basename != 
b'__init__.py':
-                    yield prefix + _to_text(os.path.splitext(b_basename)[0]), 
False
+                modname = basename
+                ispkg = True
+
+            if modname and '.' not in modname:
+                yielded.add(modname)
+                yield prefix + modname, ispkg
 
 
 def _get_collection_metadata(collection_name):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/pyproject.toml 
new/ansible_core-2.21.3/pyproject.toml
--- old/ansible_core-2.21.2/pyproject.toml      2026-07-13 21:37:14.000000000 
+0200
+++ new/ansible_core-2.21.3/pyproject.toml      2026-08-10 17:52:54.000000000 
+0200
@@ -1,5 +1,5 @@
 [build-system]
-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
+requires = ["setuptools >= 77.0.3, <= 84.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.2/test/integration/targets/async/tasks/main.yml 
new/ansible_core-2.21.3/test/integration/targets/async/tasks/main.yml
--- old/ansible_core-2.21.2/test/integration/targets/async/tasks/main.yml       
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/test/integration/targets/async/tasks/main.yml       
2026-08-10 17:52:54.000000000 +0200
@@ -287,7 +287,7 @@
 - name: Test that async has stdin
   command: >
     {{ ansible_python_interpreter|default('/usr/bin/python') }} -c 'import os; 
os.fdopen(os.dup(0), "r")'
-  async: 1
+  async: 5
   poll: 1
 
 - name: run async poll callback test playbook
@@ -314,6 +314,7 @@
 - name: test 'hidden' async, gather_facts does internal on parallel true. 
Ensure it won't freeze in check_mode due to async_status not supporting it.
   gather_facts:
     parallel: true
+    gather_timeout: 10
   timeout: 60
   check_mode: true
   when: "ansible_facts['os_family'] != 'Darwin'"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/aliases 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/aliases
--- old/ansible_core-2.21.2/test/integration/targets/collection_loader/aliases  
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.21.3/test/integration/targets/collection_loader/aliases  
2026-08-10 17:52:54.000000000 +0200
@@ -0,0 +1,3 @@
+shippable/posix/group3
+context/controller
+needs/target/collection
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/ansible_collections/ns/col/plugins/action/test_collection_loader.py
 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/ansible_collections/ns/col/plugins/action/test_collection_loader.py
--- 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/ansible_collections/ns/col/plugins/action/test_collection_loader.py
      1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/ansible_collections/ns/col/plugins/action/test_collection_loader.py
      2026-08-10 17:52:54.000000000 +0200
@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+from ansible.plugins.action import ActionBase
+
+
+class ActionModule(ActionBase):
+    def run(self, tmp=None, task_vars=None):
+        import my_module
+
+        result = super(ActionModule, self).run(tmp, task_vars)
+        result['modules'] = my_module.run()
+
+        return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/__init__.py
 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/__init__.py
--- 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/__init__.py
 1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/ansible_collections/ns/col/temp_pythonpath/my_module/__init__.py
 2026-08-10 17:52:54.000000000 +0200
@@ -0,0 +1,9 @@
+from __future__ import annotations
+
+import pkgutil
+
+from my_module import sub
+
+
+def run() -> list[str]:
+    return [m[1] for m in pkgutil.iter_modules(sub.__path__)]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/main.yml 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/main.yml
--- old/ansible_core-2.21.2/test/integration/targets/collection_loader/main.yml 
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.21.3/test/integration/targets/collection_loader/main.yml 
2026-08-10 17:52:54.000000000 +0200
@@ -0,0 +1,11 @@
+- name: run collection loader tests
+  hosts: localhost
+  tasks:
+  - name: run module which imports Python library inside collection dir
+    ns.col.test_collection_loader:
+    register: result
+
+  - name: assert module loader results
+    assert:
+      that:
+      - result.modules == ['foo']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/collection_loader/runme.sh 
new/ansible_core-2.21.3/test/integration/targets/collection_loader/runme.sh
--- old/ansible_core-2.21.2/test/integration/targets/collection_loader/runme.sh 
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.21.3/test/integration/targets/collection_loader/runme.sh 
2026-08-10 17:52:54.000000000 +0200
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -eux
+
+PLAYBOOK_DIR="${PWD}"
+
+source ../collection/setup.sh
+
+EXT=$( python -c 'import importlib.machinery as i; print(next(iter([s for s in 
i.all_suffixes() if s != ".py"])))' )
+touch "temp_pythonpath/my_module/sub/foo${EXT}"
+
+ANSIBLE_COLLECTIONS_PATH="${PWD}../../../" \
+    ANSIBLE_LOCALHOST_WARNING=false \
+    PYTHONPATH="./temp_pythonpath:${PYTHONPATH}" \
+    ansible-playbook "${PLAYBOOK_DIR}/main.yml" "$@"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/powershell_exec_wrapper/tasks/windows.yml
 
new/ansible_core-2.21.3/test/integration/targets/powershell_exec_wrapper/tasks/windows.yml
--- 
old/ansible_core-2.21.2/test/integration/targets/powershell_exec_wrapper/tasks/windows.yml
  2026-07-13 21:37:14.000000000 +0200
+++ 
new/ansible_core-2.21.3/test/integration/targets/powershell_exec_wrapper/tasks/windows.yml
  2026-08-10 17:52:54.000000000 +0200
@@ -159,3 +159,64 @@
   failed_when: >-
     iso8601_date.value != '2040-10-27T14:30:00' or
     iso8601_date.value_type != 'System.String'
+
+# https://github.com/ansible/ansible/issues/87228
+- name: Prepend 32-bit PATH for interpreter check
+  ansible.windows.win_powershell:
+    script: |
+      $ErrorActionPreference = 'Stop'
+
+      $keyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session 
Manager\Environment'
+      $sysWowPowerShell = 'C:\Windows\SysWow64\WindowsPowerShell\v1.0'
+
+      $key = Get-Item -LiteralPath $keyPath
+      $rawPath = $key.GetValue('PATH', '', 'DoNotExpandEnvironmentNames')
+      $key.Dispose()
+
+      
[System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($rawPath)
+
+      $keyParams = @{
+          Path = $keyPath
+          Name = 'PATH'
+          Value = "$sysWowPowerShell;$rawPath"
+          PropertyType = 'ExpandString'
+          Force = $true
+          Confirm = $false
+      }
+      New-ItemProperty @keyParams | Out-Null
+  register: path_info
+
+- name: Set reset PowerShell script to restore PATH to original value
+  set_fact:
+    reset_path_command: |
+      $ErrorActionPreference = 'Stop'
+
+      $keyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session 
Manager\Environment'
+      $rawPath = '{{ path_info.output[0] }}'
+
+      $keyParams = @{
+          Path = $keyPath
+          Name = 'PATH'
+          Value = $rawPath
+          PropertyType = 'ExpandString'
+          Force = $true
+          Confirm = $false
+      }
+      New-ItemProperty @keyParams | Out-Null
+
+- block:
+  - name: Reset connection to ensure refreshed env vars
+    meta: reset_connection
+
+  - name: Test that multiple PATH entries continue to work
+    get_info:
+
+  always:
+  # We use raw as we won't know if setting the PATH broke everything or not.
+  - name: Reset PATH back to the original value
+    raw: >-
+      C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
+      -EncodedCommand {{ reset_path_command | b64encode(encoding='utf-16-le')}}
+
+  - name: Reset connection to after resetting env vars
+    meta: reset_connection
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/integration/targets/rpm_key/tasks/rpm_key.yaml 
new/ansible_core-2.21.3/test/integration/targets/rpm_key/tasks/rpm_key.yaml
--- old/ansible_core-2.21.2/test/integration/targets/rpm_key/tasks/rpm_key.yaml 
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/test/integration/targets/rpm_key/tasks/rpm_key.yaml 
2026-08-10 17:52:54.000000000 +0200
@@ -238,6 +238,42 @@
       - result is success
       - result is not changed
 
+# Test armor without trailing newline 
(https://github.com/ansible/ansible/issues/87303).
+# Note that this particular issue is very dependent on the version of librpm 
installed.
+# Tolerancy of a missing newline varies across older versions, but we add this 
specific
+# test to validate our handling of the missing trailing newline.
+- name: Remove all keys from key ring
+  shell: rpm -q gpg-pubkey | xargs rpm -e
+
+- name: Read test key content
+  slurp:
+    src: "{{ test_key_path }}"
+  register: test_key_content
+
+- name: Write key file without trailing newline
+  copy:
+    content: "{{ (test_key_content['content'] | b64decode).rstrip('\n') }}"
+    dest: "{{ remote_tmp_dir }}/key_no_newline.asc"
+    mode: "0644"
+
+- name: Verify key file has no trailing newline
+  shell: test "$(tail -c 1 {{ remote_tmp_dir }}/key_no_newline.asc)" != ""
+
+- name: Import key without trailing newline
+  rpm_key:
+    state: present
+    key: "{{ remote_tmp_dir }}/key_no_newline.asc"
+
+- name: Import key without trailing newline (idempotent)
+  rpm_key:
+    state: present
+    key: "{{ remote_tmp_dir }}/key_no_newline.asc"
+  register: no_newline_idempotence
+
+- name: Verify idempotence
+  assert:
+    that: no_newline_idempotence is not changed
+
 # Test PGPv6 keys
 - name: Test PGPv6 Keys
   block:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/lib/ansible_test/_internal/host_configs.py 
new/ansible_core-2.21.3/test/lib/ansible_test/_internal/host_configs.py
--- old/ansible_core-2.21.2/test/lib/ansible_test/_internal/host_configs.py     
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/test/lib/ansible_test/_internal/host_configs.py     
2026-08-10 17:52:54.000000000 +0200
@@ -420,7 +420,9 @@
         super().apply_defaults(context, defaults)
 
         self.become = self.become or defaults.become
-        self.name = defaults.name
+
+        if not defaults.is_default:
+            self.name = defaults.name
 
     @property
     def have_root(self) -> bool:
@@ -451,7 +453,9 @@
         super().apply_defaults(context, defaults)
 
         self.connection = self.connection or defaults.connection
-        self.name = defaults.name
+
+        if not defaults.is_default:
+            self.name = defaults.name
 
 
 @dataclasses.dataclass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.21.2/test/units/cli/test_galaxy.py 
new/ansible_core-2.21.3/test/units/cli/test_galaxy.py
--- old/ansible_core-2.21.2/test/units/cli/test_galaxy.py       2026-07-13 
21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/test/units/cli/test_galaxy.py       2026-08-10 
17:52:54.000000000 +0200
@@ -918,10 +918,21 @@
     assert mock_install.call_args[0][6] is False  # force_deps
 
 
-def test_collection_install_with_url(monkeypatch, collection_install):
+def test_collection_install_with_url(
+    monkeypatch: pytest.MonkeyPatch,
+    collection_install: tuple[MagicMock, MagicMock, str]
+) -> None:
     mock_install, dummy, output_dir = collection_install
 
-    mock_open = MagicMock(return_value=BytesIO())
+    class MockHTTPResponse:
+        def __init__(self) -> None:
+            self._stream = BytesIO()
+            self.length = 'UNKNOWN'
+
+        def read(self, size: int = -1) -> bytes:
+            return self._stream.read(size)
+
+    mock_open = MagicMock(return_value=MockHTTPResponse())
     monkeypatch.setattr(collection.concrete_artifact_manager, 'open_url', 
mock_open)
 
     mock_metadata = MagicMock(return_value={'namespace': 'foo', 'name': 'bar', 
'version': 'v1.0.0'})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.21.2/test/units/galaxy/test_collection.py 
new/ansible_core-2.21.3/test/units/galaxy/test_collection.py
--- old/ansible_core-2.21.2/test/units/galaxy/test_collection.py        
2026-07-13 21:37:14.000000000 +0200
+++ new/ansible_core-2.21.3/test/units/galaxy/test_collection.py        
2026-08-10 17:52:54.000000000 +0200
@@ -890,7 +890,26 @@
         % galaxy_server.api_server
 
 
-def test_download_file(tmp_path_factory, monkeypatch):
+class MockHTTPResponses:
+    def __init__(self, responses: list[tuple[bytes, int | None]]) -> None:
+        self._body, self.length = responses[0]
+        self._responses = responses[1:]
+        self._stream = BytesIO(self._body)
+
+    def read(self, size: int = -1) -> bytes:
+        data = self._stream.read(size)
+
+        if not data:
+            # reset the stream to simulate retries
+            if self._responses:
+                self._body, self.length = self._responses[0]
+                del self._responses[0]
+            self._stream = BytesIO(self._body)
+
+        return data
+
+
+def test_download_file(tmp_path_factory: pytest.TempPathFactory, monkeypatch: 
pytest.MonkeyPatch) -> None:
     temp_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections'))
 
     data = b"\x00\x01\x02\x03"
@@ -898,7 +917,7 @@
     sha256_hash.update(data)
 
     mock_open = MagicMock()
-    mock_open.return_value = BytesIO(data)
+    mock_open.return_value = MockHTTPResponses([(data, len(data))])
     monkeypatch.setattr(collection.concrete_artifact_manager, 'open_url', 
mock_open)
 
     expected = temp_dir
@@ -913,13 +932,13 @@
     assert mock_open.mock_calls[0][1][0] == 'http://google.com/file'
 
 
-def test_download_file_hash_mismatch(tmp_path_factory, monkeypatch):
+def test_download_file_hash_mismatch(tmp_path_factory: pytest.TempPathFactory, 
monkeypatch: pytest.MonkeyPatch) -> None:
     temp_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections'))
 
     data = b"\x00\x01\x02\x03"
 
     mock_open = MagicMock()
-    mock_open.return_value = BytesIO(data)
+    mock_open.return_value = MockHTTPResponses([(data, len(data))])
     monkeypatch.setattr(collection.concrete_artifact_manager, 'open_url', 
mock_open)
 
     expected = "Mismatch artifact hash with downloaded file"
@@ -927,6 +946,35 @@
         collection._download_file('http://google.com/file', temp_dir, 'bad', 
True)
 
 
+def test_download_file_incomplete_read(
+    tmp_path_factory: pytest.TempPathFactory,
+    monkeypatch: pytest.MonkeyPatch
+) -> None:
+    temp_dir = to_bytes(tmp_path_factory.mktemp('test-ÅÑŚÌβŁÈ Collections'))
+
+    incomplete_data = b"\x00\x01\x02\x03"
+    data = incomplete_data + b"\x04"
+    sha256_hash = sha256(data)
+
+    # Test retries succeed
+    responses = [(incomplete_data, len(data)), (data, len(data))]
+    mock_open = MagicMock(return_value=MockHTTPResponses(responses))
+    monkeypatch.setattr(collection.concrete_artifact_manager, 'open_url', 
mock_open)
+
+    collection._download_file('http://google.com/file', temp_dir, 
sha256_hash.hexdigest(), True)
+
+    # Test error is correct
+    mock_open.return_value = MockHTTPResponses([(data, len(data) + 1)])
+    monkeypatch.setattr(collection.concrete_artifact_manager, 'open_url', 
mock_open)
+
+    expected_error = re.escape(
+        "Downloading http://google.com/file failed: "
+        "IncompleteRead(5 bytes read, 1 more expected)"
+    )
+    with pytest.raises(AnsibleError, match=expected_error):
+        collection._download_file('http://google.com/file', temp_dir, 
sha256_hash.hexdigest(), True)
+
+
 def test_extract_tar_file_invalid_hash(tmp_tarfile):
     temp_dir, tfile, filename, dummy = tmp_tarfile
 

++++++ ansible_core-2.21.2.tar.gz.sha256 -> ansible_core-2.21.3.tar.gz.sha256 
++++++
--- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.21.2.tar.gz.sha256   
2026-07-14 13:51:54.043041740 +0200
+++ 
/work/SRC/openSUSE:Factory/.ansible-core.new.17972/ansible_core-2.21.3.tar.gz.sha256
        2026-08-11 17:18:12.388768475 +0200
@@ -1 +1 @@
-c6139e662eda1bfb5af16029f12afa5edfc7f95f8cd44281b1bdb434a6d9dc70  
ansible_core-2.21.2.tar.gz
+4194fbd82273cbacfd06d86d74d2d7168c3c4b8426c03e93562cd7217f811ae1  
ansible_core-2.21.3.tar.gz

Reply via email to