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 2024-09-16 17:40:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ansible-core (Old)
 and      /work/SRC/openSUSE:Factory/.ansible-core.new.29891 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ansible-core"

Mon Sep 16 17:40:37 2024 rev:32 rq:1200966 version:2.17.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes        
2024-08-15 09:58:44.496008287 +0200
+++ /work/SRC/openSUSE:Factory/.ansible-core.new.29891/ansible-core.changes     
2024-09-16 17:40:50.251501636 +0200
@@ -1,0 +2,38 @@
+Wed Sep 11 05:24:58 UTC 2024 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- update to 2.17.4:
+  
https://github.com/ansible/ansible/blob/v2.17.4/changelogs/CHANGELOG-v2.17.rst
+  * Bugfixes
+    - Fix SemanticVersion.parse() to store the version string so
+      that __repr__ reports it instead of None (#83831).
+    - Fix an issue where registered variable was not available for
+      templating in loop_control.label on skipped looped tasks
+      (#83619)
+    - Fix for meta tasks breaking host/fork affinity with
+      host_pinned strategy (#83294)
+    - Fix using the current task's directory for looking up
+      relative paths within roles (#82695).
+    - atomic_move - fix using the setgid bit on the parent
+      directory when creating files (#46742, #67177).
+    - connection plugins using the 'extras' option feature would
+      need variables to match the plugin's loaded name, sometimes
+      requiring fqcn, which is not the same as the
+      documented/declared/expected variables. Now we fall back to
+      the 'basename' of the fqcn, but plugin authors can still set
+      the expected value directly.
+    - csvfile lookup - give an error when no search term is
+      provided using modern config syntax (#83689).
+    - include_tasks - Display location when attempting to load a
+      task list where include_* did not specify any value - #83874
+    - powershell - Improve CLIXML decoding to decode all control
+      characters and unicode characters that are encoded as
+      surrogate pairs.
+    - psrp - Fix bug when attempting to fetch a file path that
+      contains special glob characters like []
+    - runtime-metadata sanity test - do not crash on deprecations
+      if galaxy.yml contains an empty version field (#83831).
+    - ssh - Fix bug when attempting to fetch a file path with
+      characters that should be quoted when using the piped
+      transfer method
+
+-------------------------------------------------------------------

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

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

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

Other differences:
------------------
++++++ ansible-core.spec ++++++
--- /var/tmp/diff_new_pack.4O19u8/_old  2024-09-16 17:40:51.503553859 +0200
+++ /var/tmp/diff_new_pack.4O19u8/_new  2024-09-16 17:40:51.507554025 +0200
@@ -38,7 +38,7 @@
 %endif
 
 Name:           ansible-core
-Version:        2.17.3
+Version:        2.17.4
 Release:        0
 Summary:        Radically simple IT automation
 License:        GPL-3.0-or-later

++++++ ansible_core-2.17.3.tar.gz -> ansible_core-2.17.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/PKG-INFO 
new/ansible_core-2.17.4/PKG-INFO
--- old/ansible_core-2.17.3/PKG-INFO    2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/PKG-INFO    2024-09-09 21:26:24.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: ansible-core
-Version: 2.17.3
+Version: 2.17.4
 Summary: Radically simple IT automation
 Home-page: https://ansible.com/
 Author: Ansible, Inc.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/changelogs/CHANGELOG-v2.17.rst 
new/ansible_core-2.17.4/changelogs/CHANGELOG-v2.17.rst
--- old/ansible_core-2.17.3/changelogs/CHANGELOG-v2.17.rst      2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/changelogs/CHANGELOG-v2.17.rst      2024-09-09 
21:26:24.000000000 +0200
@@ -4,6 +4,31 @@
 
 .. contents:: Topics
 
+v2.17.4
+=======
+
+Release Summary
+---------------
+
+| Release Date: 2024-09-09
+| `Porting Guide 
<https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+Bugfixes
+--------
+
+- Fix ``SemanticVersion.parse()`` to store the version string so that 
``__repr__`` reports it instead of ``None`` 
(https://github.com/ansible/ansible/pull/83831).
+- Fix an issue where registered variable was not available for templating in 
``loop_control.label`` on skipped looped tasks 
(https://github.com/ansible/ansible/issues/83619)
+- Fix for ``meta`` tasks breaking host/fork affinity with ``host_pinned`` 
strategy (https://github.com/ansible/ansible/issues/83294)
+- Fix using the current task's directory for looking up relative paths within 
roles (https://github.com/ansible/ansible/issues/82695).
+- atomic_move - fix using the setgid bit on the parent directory when creating 
files (https://github.com/ansible/ansible/issues/46742, 
https://github.com/ansible/ansible/issues/67177).
+- connection plugins using the 'extras' option feature would need variables to 
match the plugin's loaded name, sometimes requiring fqcn, which is not the same 
as the documented/declared/expected variables. Now we fall back to the 
'basename' of the fqcn, but plugin authors can still set the expected value 
directly.
+- csvfile lookup - give an error when no search term is provided using modern 
config syntax (https://github.com/ansible/ansible/issues/83689).
+- include_tasks - Display location when attempting to load a task list where 
``include_*`` did not specify any value - 
https://github.com/ansible/ansible/issues/83874
+- powershell - Improve CLIXML decoding to decode all control characters and 
unicode characters that are encoded as surrogate pairs.
+- psrp - Fix bug when attempting to fetch a file path that contains special 
glob characters like ``[]``
+- runtime-metadata sanity test - do not crash on deprecations if 
``galaxy.yml`` contains an empty ``version`` field 
(https://github.com/ansible/ansible/pull/83831).
+- ssh - Fix bug when attempting to fetch a file path with characters that 
should be quoted when using the ``piped`` transfer method
+
 v2.17.3
 =======
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/changelogs/changelog.yaml 
new/ansible_core-2.17.4/changelogs/changelog.yaml
--- old/ansible_core-2.17.3/changelogs/changelog.yaml   2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/changelogs/changelog.yaml   2024-09-09 
21:26:24.000000000 +0200
@@ -825,3 +825,63 @@
     - ansible-test-vyos.yml
     - mc_fix.yml
     release_date: '2024-08-05'
+  2.17.4:
+    changes:
+      release_summary: '| Release Date: 2024-09-09
+
+        | `Porting Guide 
<https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+        '
+    codename: Gallows Pole
+    fragments:
+    - 2.17.4_summary.yaml
+    release_date: '2024-09-09'
+  2.17.4rc1:
+    changes:
+      bugfixes:
+      - Fix ``SemanticVersion.parse()`` to store the version string so that 
``__repr__``
+        reports it instead of ``None`` 
(https://github.com/ansible/ansible/pull/83831).
+      - Fix an issue where registered variable was not available for 
templating in
+        ``loop_control.label`` on skipped looped tasks 
(https://github.com/ansible/ansible/issues/83619)
+      - Fix for ``meta`` tasks breaking host/fork affinity with 
``host_pinned`` strategy
+        (https://github.com/ansible/ansible/issues/83294)
+      - Fix using the current task's directory for looking up relative paths 
within
+        roles (https://github.com/ansible/ansible/issues/82695).
+      - atomic_move - fix using the setgid bit on the parent directory when 
creating
+        files (https://github.com/ansible/ansible/issues/46742, 
https://github.com/ansible/ansible/issues/67177).
+      - connection plugins using the 'extras' option feature would need 
variables
+        to match the plugin's loaded name, sometimes requiring fqcn, which is 
not
+        the same as the documented/declared/expected variables. Now we fall 
back to
+        the 'basename' of the fqcn, but plugin authors can still set the 
expected
+        value directly.
+      - csvfile lookup - give an error when no search term is provided using 
modern
+        config syntax (https://github.com/ansible/ansible/issues/83689).
+      - include_tasks - Display location when attempting to load a task list 
where
+        ``include_*`` did not specify any value - 
https://github.com/ansible/ansible/issues/83874
+      - powershell - Improve CLIXML decoding to decode all control characters 
and
+        unicode characters that are encoded as surrogate pairs.
+      - psrp - Fix bug when attempting to fetch a file path that contains 
special
+        glob characters like ``[]``
+      - runtime-metadata sanity test - do not crash on deprecations if 
``galaxy.yml``
+        contains an empty ``version`` field 
(https://github.com/ansible/ansible/pull/83831).
+      - ssh - Fix bug when attempting to fetch a file path with characters 
that should
+        be quoted when using the ``piped`` transfer method
+      release_summary: '| Release Date: 2024-09-03
+
+        | `Porting Guide 
<https://docs.ansible.com/ansible-core/2.17/porting_guides/porting_guide_core_2.17.html>`__
+
+        '
+    codename: Gallows Pole
+    fragments:
+    - 2.17.4rc1_summary.yaml
+    - 46742-atomic_move-fix-setgid.yml
+    - 83294-meta-host_pinned-affinity.yml
+    - 83619-loop-label-register.yml
+    - 83831-runtime-metadata-fix.yml
+    - 83874-include-parse-error-location.yml
+    - dwim_is_role_fix_task_relative.yml
+    - extras_fix.yml
+    - fetch-filename.yml
+    - fix-inconsistent-csvfile-missing-search-error.yml
+    - powershell-clixml.yml
+    release_date: '2024-09-03'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/lib/ansible/config/base.yml 
new/ansible_core-2.17.4/lib/ansible/config/base.yml
--- old/ansible_core-2.17.3/lib/ansible/config/base.yml 2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/config/base.yml 2024-09-09 
21:26:24.000000000 +0200
@@ -948,7 +948,7 @@
     - This was introduced as a way to reset role variables to default values 
if a role is used more than once
       in a playbook.
     - Starting in version '2.17' M(ansible.builtin.include_roles) and 
M(ansible.builtin.import_roles) can
-      indivudually override this via the C(public) parameter.
+      individually override this via the C(public) parameter.
     - Included roles only make their variables public at execution, unlike 
imported roles which happen at playbook compile time.
   env: [{name: ANSIBLE_PRIVATE_ROLE_VARS}]
   ini:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/executor/task_executor.py 
new/ansible_core-2.17.4/lib/ansible/executor/task_executor.py
--- old/ansible_core-2.17.3/lib/ansible/executor/task_executor.py       
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/executor/task_executor.py       
2024-09-09 21:26:24.000000000 +0200
@@ -31,7 +31,7 @@
 from ansible.utils.unsafe_proxy import to_unsafe_text, wrap_var
 from ansible.vars.clean import namespace_facts, clean_facts
 from ansible.utils.display import Display
-from ansible.utils.vars import combine_vars, isidentifier
+from ansible.utils.vars import combine_vars
 
 display = Display()
 
@@ -332,6 +332,13 @@
             (self._task, tmp_task) = (tmp_task, self._task)
             (self._play_context, tmp_play_context) = (tmp_play_context, 
self._play_context)
             res = self._execute(variables=task_vars)
+
+            if self._task.register:
+                # Ensure per loop iteration results are registered in case 
`_execute()`
+                # returns early (when conditional, failure, ...).
+                # This is needed in case the registered variable is used in 
the loop label template.
+                task_vars[self._task.register] = res
+
             task_fields = self._task.dump_attrs()
             (self._task, tmp_task) = (tmp_task, self._task)
             (self._play_context, tmp_play_context) = (tmp_play_context, 
self._play_context)
@@ -657,9 +664,6 @@
             # update the local copy of vars with the registered value, if 
specified,
             # or any facts which may have been generated by the module 
execution
             if self._task.register:
-                if not isidentifier(self._task.register):
-                    raise AnsibleError("Invalid variable name in 'register' 
specified: '%s'" % self._task.register)
-
                 vars_copy[self._task.register] = result
 
             if self._task.async_val > 0:
@@ -1048,7 +1052,7 @@
         # add extras if plugin supports them
         if getattr(self._connection, 'allow_extras', False):
             for k in variables:
-                if k.startswith('ansible_%s_' % self._connection._load_name) 
and k not in options:
+                if k.startswith('ansible_%s_' % 
self._connection.extras_prefix) and k not in options:
                     options['_extras'][k] = templar.template(variables[k])
 
         task_keys = self._task.dump_attrs()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/module_utils/ansible_release.py 
new/ansible_core-2.17.4/lib/ansible/module_utils/ansible_release.py
--- old/ansible_core-2.17.3/lib/ansible/module_utils/ansible_release.py 
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/module_utils/ansible_release.py 
2024-09-09 21:26:24.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.17.3'
+__version__ = '2.17.4'
 __author__ = 'Ansible, Inc.'
 __codename__ = "Gallows Pole"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/module_utils/basic.py 
new/ansible_core-2.17.4/lib/ansible/module_utils/basic.py
--- old/ansible_core-2.17.3/lib/ansible/module_utils/basic.py   2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/module_utils/basic.py   2024-09-09 
21:26:24.000000000 +0200
@@ -1686,8 +1686,12 @@
             umask = os.umask(0)
             os.umask(umask)
             os.chmod(b_dest, S_IRWU_RWG_RWO & ~umask)
+            dest_dir_stat = os.stat(os.path.dirname(b_dest))
             try:
-                os.chown(b_dest, os.geteuid(), os.getegid())
+                if dest_dir_stat.st_mode & stat.S_ISGID:
+                    os.chown(b_dest, os.geteuid(), dest_dir_stat.st_gid)
+                else:
+                    os.chown(b_dest, os.geteuid(), os.getegid())
             except OSError:
                 # We're okay with trying our best here.  If the user is not
                 # root (or old Unices) they won't be able to chown.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/parsing/dataloader.py 
new/ansible_core-2.17.4/lib/ansible/parsing/dataloader.py
--- old/ansible_core-2.17.3/lib/ansible/parsing/dataloader.py   2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/parsing/dataloader.py   2024-09-09 
21:26:24.000000000 +0200
@@ -329,11 +329,10 @@
                 if (is_role or self._is_role(path)) and 
b_pb_base_dir.endswith(b'/tasks'):
                     search.append(os.path.join(os.path.dirname(b_pb_base_dir), 
b_dirname, b_source))
                     search.append(os.path.join(b_pb_base_dir, b_source))
-                else:
-                    # don't add dirname if user already is using it in source
-                    if b_source.split(b'/')[0] != dirname:
-                        search.append(os.path.join(b_upath, b_dirname, 
b_source))
-                    search.append(os.path.join(b_upath, b_source))
+                # don't add dirname if user already is using it in source
+                if b_source.split(b'/')[0] != dirname:
+                    search.append(os.path.join(b_upath, b_dirname, b_source))
+                search.append(os.path.join(b_upath, b_source))
 
             # always append basedir as last resort
             # don't add dirname if user already is using it in source
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/lib/ansible/playbook/task.py 
new/ansible_core-2.17.4/lib/ansible/playbook/task.py
--- old/ansible_core-2.17.3/lib/ansible/playbook/task.py        2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/playbook/task.py        2024-09-09 
21:26:24.000000000 +0200
@@ -37,6 +37,7 @@
 from ansible.utils.collection_loader import AnsibleCollectionConfig
 from ansible.utils.display import Display
 from ansible.utils.sentinel import Sentinel
+from ansible.utils.vars import isidentifier
 
 __all__ = ['Task']
 
@@ -274,6 +275,10 @@
         if not isinstance(value, list):
             setattr(self, name, [value])
 
+    def _validate_register(self, attr, name, value):
+        if value is not None and not isidentifier(value):
+            raise AnsibleParserError(f"Invalid variable name in 'register' 
specified: '{value}'")
+
     def post_validate(self, templar):
         '''
         Override of base class post_validate, to also do final validation on
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/playbook/task_include.py 
new/ansible_core-2.17.4/lib/ansible/playbook/task_include.py
--- old/ansible_core-2.17.3/lib/ansible/playbook/task_include.py        
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/playbook/task_include.py        
2024-09-09 21:26:24.000000000 +0200
@@ -74,7 +74,7 @@
         if not task.args.get('_raw_params'):
             task.args['_raw_params'] = task.args.pop('file', None)
             if not task.args['_raw_params']:
-                raise AnsibleParserError('No file specified for %s' % 
task.action)
+                raise AnsibleParserError('No file specified for %s' % 
task.action, obj=data)
 
         apply_attrs = task.args.get('apply', {})
         if apply_attrs and task.action not in C._ACTION_INCLUDE_TASKS:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/lib/ansible/plugins/__init__.py 
new/ansible_core-2.17.4/lib/ansible/plugins/__init__.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/__init__.py     2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/__init__.py     2024-09-09 
21:26:24.000000000 +0200
@@ -50,16 +50,23 @@
 
 class AnsiblePlugin(ABC):
 
-    # allow extra passthrough parameters
-    allow_extras = False
-
     # Set by plugin loader
     _load_name: str
 
+    # allow extra passthrough parameters
+    allow_extras: bool = False
+    _extras_prefix: str | None = None
+
     def __init__(self):
         self._options = {}
         self._defs = None
 
+    @property
+    def extras_prefix(self):
+        if not self._extras_prefix:
+            self._extras_prefix = self._load_name.split('.')[-1]
+        return self._extras_prefix
+
     def matches_name(self, possible_names):
         possible_fqcns = set()
         for name in possible_names:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/connection/psrp.py 
new/ansible_core-2.17.4/lib/ansible/plugins/connection/psrp.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/connection/psrp.py      
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/connection/psrp.py      
2024-09-09 21:26:24.000000000 +0200
@@ -632,39 +632,41 @@
         buffer_size = max_b64_size - (max_b64_size % 1024)
 
         # setup the file stream with read only mode
-        setup_script = '''$ErrorActionPreference = "Stop"
-$path = '%s'
+        setup_script = '''param([string]$Path)
+$ErrorActionPreference = "Stop"
 
-if (Test-Path -Path $path -PathType Leaf) {
+if (Test-Path -LiteralPath $path -PathType Leaf) {
     $fs = New-Object -TypeName System.IO.FileStream -ArgumentList @(
         $path,
         [System.IO.FileMode]::Open,
         [System.IO.FileAccess]::Read,
         [System.IO.FileShare]::Read
     )
-    $buffer_size = %d
 } elseif (Test-Path -Path $path -PathType Container) {
     Write-Output -InputObject "[DIR]"
 } else {
     Write-Error -Message "$path does not exist"
     $host.SetShouldExit(1)
-}''' % (self._shell._escape(in_path), buffer_size)
+}'''
 
         # read the file stream at the offset and return the b64 string
-        read_script = '''$ErrorActionPreference = "Stop"
-$fs.Seek(%d, [System.IO.SeekOrigin]::Begin) > $null
-$buffer = New-Object -TypeName byte[] -ArgumentList $buffer_size
-$bytes_read = $fs.Read($buffer, 0, $buffer_size)
-
-if ($bytes_read -gt 0) {
-    $bytes = $buffer[0..($bytes_read - 1)]
-    Write-Output -InputObject ([System.Convert]::ToBase64String($bytes))
+        read_script = '''param([int64]$Offset, [int]$BufferSize)
+$ErrorActionPreference = "Stop"
+$fs.Seek($Offset, [System.IO.SeekOrigin]::Begin) > $null
+$buffer = New-Object -TypeName byte[] -ArgumentList $BufferSize
+$read = $fs.Read($buffer, 0, $buffer.Length)
+
+if ($read -gt 0) {
+    [System.Convert]::ToBase64String($buffer, 0, $read)
 }'''
 
         # need to run the setup script outside of the local scope so the
         # file stream stays active between fetch operations
-        rc, stdout, stderr = self._exec_psrp_script(setup_script,
-                                                    use_local_scope=False)
+        rc, stdout, stderr = self._exec_psrp_script(
+            setup_script,
+            use_local_scope=False,
+            arguments=[in_path],
+        )
         if rc != 0:
             raise AnsibleError("failed to setup file stream for fetch '%s': %s"
                                % (out_path, to_native(stderr)))
@@ -679,7 +681,10 @@
             while True:
                 display.vvvvv("PSRP FETCH %s to %s (offset=%d" %
                               (in_path, out_path, offset), 
host=self._psrp_host)
-                rc, stdout, stderr = self._exec_psrp_script(read_script % 
offset)
+                rc, stdout, stderr = self._exec_psrp_script(
+                    read_script,
+                    arguments=[offset, buffer_size],
+                )
                 if rc != 0:
                     raise AnsibleError("failed to transfer file to '%s': %s"
                                        % (out_path, to_native(stderr)))
@@ -813,7 +818,7 @@
         script: str,
         input_data: bytes | str | t.Iterable | None = None,
         use_local_scope: bool = True,
-        arguments: t.Iterable[str] | None = None,
+        arguments: t.Iterable[t.Any] | None = None,
     ) -> tuple[int, bytes, bytes]:
         # Check if there's a command on the current pipeline that still needs 
to be closed.
         if self._last_pipeline:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/connection/ssh.py 
new/ansible_core-2.17.4/lib/ansible/plugins/connection/ssh.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/connection/ssh.py       
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/connection/ssh.py       
2024-09-09 21:26:24.000000000 +0200
@@ -1250,7 +1250,7 @@
                 if sftp_action == 'get':
                     # we pass sudoable=False to disable pty allocation, which
                     # would end up mixing stdout/stderr and screwing with 
newlines
-                    (returncode, stdout, stderr) = self.exec_command('dd if=%s 
bs=%s' % (in_path, BUFSIZE), sudoable=False)
+                    (returncode, stdout, stderr) = self.exec_command('dd if=%s 
bs=%s' % (self._shell.quote(in_path), BUFSIZE), sudoable=False)
                     with open(to_bytes(out_path, 
errors='surrogate_or_strict'), 'wb+') as out_file:
                         out_file.write(stdout)
                 else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/lookup/csvfile.py 
new/ansible_core-2.17.4/lib/ansible/plugins/lookup/csvfile.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/lookup/csvfile.py       
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/lookup/csvfile.py       
2024-09-09 21:26:24.000000000 +0200
@@ -12,6 +12,7 @@
       - The csvfile lookup reads the contents of a file in CSV 
(comma-separated value) format.
         The lookup looks for the row where the first column matches keyname 
(which can be multiple words)
         and returns the value in the O(col) column (default 1, which indexed 
from 0 means the second column in the file).
+      - At least one keyname is required, provided as a positional argument(s) 
to the lookup.
     options:
       col:
         description:  column to return (0 indexed).
@@ -63,6 +64,22 @@
   vars:
     csvline: "{{ lookup('ansible.builtin.csvfile', bgp_neighbor_ip, 
file='bgp_neighbors.csv', delimiter=',') }}"
   delegate_to: localhost
+
+# Contents of debug.csv
+# test1 ret1.1 ret2.1
+# test2 ret1.2 ret2.2
+# test3 ret1.3 ret2.3
+
+- name: "Lookup multiple keynames in the first column (index 0), returning the 
values from the second column (index 1)"
+  debug:
+    msg: "{{ lookup('csvfile', 'test1', 'test2', file='debug.csv', delimiter=' 
') }}"
+
+- name: Lookup multiple keynames using old style syntax
+  debug:
+    msg: "{{ lookup('csvfile', term1, term2) }}"
+  vars:
+    term1: "test1 file=debug.csv delimiter=' '"
+    term2: "test2 file=debug.csv delimiter=' '"
 """
 
 RETURN = """
@@ -150,6 +167,9 @@
         # populate options
         paramvals = self.get_options()
 
+        if not terms:
+            raise AnsibleError('Search key is required but was not found')
+
         for term in terms:
             kv = parse_kv(term)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/shell/powershell.py 
new/ansible_core-2.17.4/lib/ansible/plugins/shell/powershell.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/shell/powershell.py     
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/shell/powershell.py     
2024-09-09 21:26:24.000000000 +0200
@@ -25,35 +25,70 @@
 from ansible.module_utils.common.text.converters import to_bytes, to_text
 from ansible.plugins.shell import ShellBase
 
+# This is weird, we are matching on byte sequences that match the utf-16-be
+# matches for '_x(a-fA-F0-9){4}_'. The \x00 and {8} will match the hex sequence
+# when it is encoded as utf-16-be.
+_STRING_DESERIAL_FIND = re.compile(rb"\x00_\x00x([\x00(a-fA-F0-9)]{8})\x00_")
 
 _common_args = ['PowerShell', '-NoProfile', '-NonInteractive', 
'-ExecutionPolicy', 'Unrestricted']
 
 
-def _parse_clixml(data, stream="Error"):
+def _parse_clixml(data: bytes, stream: str = "Error") -> bytes:
     """
     Takes a byte string like '#< CLIXML\r\n<Objs...' and extracts the stream
     message encoded in the XML data. CLIXML is used by PowerShell to encode
     multiple objects in stderr.
     """
-    lines = []
+    lines: list[str] = []
+
+    # A serialized string will serialize control chars and surrogate pairs as
+    # _xDDDD_ values where DDDD is the hex representation of a big endian
+    # UTF-16 code unit. As a surrogate pair uses 2 UTF-16 code units, we need
+    # to operate our text replacement on the utf-16-be byte encoding of the raw
+    # text. This allows us to replace the _xDDDD_ values with the actual byte
+    # values and then decode that back to a string from the utf-16-be bytes.
+    def rplcr(matchobj: re.Match) -> bytes:
+        match_hex = matchobj.group(1)
+        hex_string = match_hex.decode("utf-16-be")
+        return base64.b16decode(hex_string.upper())
 
     # There are some scenarios where the stderr contains a nested CLIXML 
element like
     # '<# CLIXML\r\n<# CLIXML\r\n<Objs>...</Objs><Objs>...</Objs>'.
     # Parse each individual <Objs> element and add the error strings to our 
stderr list.
     # https://github.com/ansible/ansible/issues/69550
     while data:
-        end_idx = data.find(b"</Objs>") + 7
-        current_element = data[data.find(b"<Objs "):end_idx]
+        start_idx = data.find(b"<Objs ")
+        end_idx = data.find(b"</Objs>")
+        if start_idx == -1 or end_idx == -1:
+            break
+
+        end_idx += 7
+        current_element = data[start_idx:end_idx]
         data = data[end_idx:]
 
         clixml = ET.fromstring(current_element)
         namespace_match = re.match(r'{(.*)}', clixml.tag)
-        namespace = "{%s}" % namespace_match.group(1) if namespace_match else 
""
+        namespace = f"{{{namespace_match.group(1)}}}" if namespace_match else 
""
+
+        entries = clixml.findall("./%sS" % namespace)
+        if not entries:
+            continue
+
+        # If this is a new CLIXML element, add a newline to separate the 
messages.
+        if lines:
+            lines.append("\r\n")
+
+        for string_entry in entries:
+            actual_stream = string_entry.attrib.get('S', None)
+            if actual_stream != stream:
+                continue
+
+            b_line = (string_entry.text or "").encode("utf-16-be")
+            b_escaped = re.sub(_STRING_DESERIAL_FIND, rplcr, b_line)
 
-        strings = clixml.findall("./%sS" % namespace)
-        lines.extend([e.text.replace('_x000D__x000A_', '') for e in strings if 
e.attrib.get('S') == stream])
+            lines.append(b_escaped.decode("utf-16-be", errors="surrogatepass"))
 
-    return to_bytes('\r\n'.join(lines))
+    return to_bytes(''.join(lines), errors="surrogatepass")
 
 
 class ShellModule(ShellBase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/strategy/__init__.py 
new/ansible_core-2.17.4/lib/ansible/plugins/strategy/__init__.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/strategy/__init__.py    
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/strategy/__init__.py    
2024-09-09 21:26:24.000000000 +0200
@@ -54,7 +54,7 @@
 from ansible.utils.fqcn import add_internal_fqcns
 from ansible.utils.unsafe_proxy import wrap_var
 from ansible.utils.sentinel import Sentinel
-from ansible.utils.vars import combine_vars, isidentifier
+from ansible.utils.vars import combine_vars
 from ansible.vars.clean import strip_internal_keys, module_response_deepcopy
 
 display = Display()
@@ -766,10 +766,6 @@
 
             # register final results
             if original_task.register:
-
-                if not isidentifier(original_task.register):
-                    raise AnsibleError("Invalid variable name in 'register' 
specified: '%s'" % original_task.register)
-
                 host_list = self.get_task_hosts(iterator, original_host, 
original_task)
 
                 clean_copy = 
strip_internal_keys(module_response_deepcopy(task_result._result))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible/plugins/strategy/free.py 
new/ansible_core-2.17.4/lib/ansible/plugins/strategy/free.py
--- old/ansible_core-2.17.3/lib/ansible/plugins/strategy/free.py        
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/plugins/strategy/free.py        
2024-09-09 21:26:24.000000000 +0200
@@ -95,6 +95,7 @@
 
             # try and find an unblocked host with a task to run
             host_results = []
+            meta_task_dummy_results_count = 0
             while True:
                 host = hosts_left[last_host]
                 display.debug("next free host: %s" % host)
@@ -181,6 +182,9 @@
                                 continue
 
                         if task.action in C._ACTION_META:
+                            if self._host_pinned:
+                                meta_task_dummy_results_count += 1
+                                workers_free -= 1
                             self._execute_meta(task, play_context, iterator, 
target_host=host)
                             self._blocked_hosts[host_name] = False
                         else:
@@ -220,7 +224,7 @@
             host_results.extend(results)
 
             # each result is counted as a worker being free again
-            workers_free += len(results)
+            workers_free += len(results) + meta_task_dummy_results_count
 
             self.update_active_connections(results)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/lib/ansible/release.py 
new/ansible_core-2.17.4/lib/ansible/release.py
--- old/ansible_core-2.17.3/lib/ansible/release.py      2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/release.py      2024-09-09 
21:26:24.000000000 +0200
@@ -17,6 +17,6 @@
 
 from __future__ import annotations
 
-__version__ = '2.17.3'
+__version__ = '2.17.4'
 __author__ = 'Ansible, Inc.'
 __codename__ = "Gallows Pole"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/lib/ansible/utils/version.py 
new/ansible_core-2.17.4/lib/ansible/utils/version.py
--- old/ansible_core-2.17.3/lib/ansible/utils/version.py        2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible/utils/version.py        2024-09-09 
21:26:24.000000000 +0200
@@ -190,6 +190,7 @@
             raise ValueError("invalid semantic version '%s'" % vstring)
 
         (major, minor, patch, prerelease, buildmetadata) = match.group(1, 2, 
3, 4, 5)
+        self.vstring = vstring
         self.major = int(major)
         self.minor = int(minor)
         self.patch = int(patch)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible_core.egg-info/PKG-INFO 
new/ansible_core-2.17.4/lib/ansible_core.egg-info/PKG-INFO
--- old/ansible_core-2.17.3/lib/ansible_core.egg-info/PKG-INFO  2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible_core.egg-info/PKG-INFO  2024-09-09 
21:26:24.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: ansible-core
-Version: 2.17.3
+Version: 2.17.4
 Summary: Radically simple IT automation
 Home-page: https://ansible.com/
 Author: Ansible, Inc.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/lib/ansible_core.egg-info/SOURCES.txt 
new/ansible_core-2.17.4/lib/ansible_core.egg-info/SOURCES.txt
--- old/ansible_core-2.17.3/lib/ansible_core.egg-info/SOURCES.txt       
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/lib/ansible_core.egg-info/SOURCES.txt       
2024-09-09 21:26:24.000000000 +0200
@@ -1060,6 +1060,14 @@
 test/integration/targets/ansible-test-sanity-replace-urlopen/runme.sh
 
test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/do-not-check-me.py
 
test/integration/targets/ansible-test-sanity-replace-urlopen/ansible_collections/ns/col/plugins/modules/check-me.py
+test/integration/targets/ansible-test-sanity-runtime-metadata/aliases
+test/integration/targets/ansible-test-sanity-runtime-metadata/expected-no_version.txt
+test/integration/targets/ansible-test-sanity-runtime-metadata/expected-version.txt
+test/integration/targets/ansible-test-sanity-runtime-metadata/runme.sh
+test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/galaxy.yml
+test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/meta/runtime.yml
+test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/galaxy.yml
+test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/meta/runtime.yml
 test/integration/targets/ansible-test-sanity-shebang/aliases
 test/integration/targets/ansible-test-sanity-shebang/expected.txt
 test/integration/targets/ansible-test-sanity-shebang/runme.sh
@@ -1740,6 +1748,7 @@
 test/integration/targets/copy/tasks/main.yml
 test/integration/targets/copy/tasks/no_log.yml
 test/integration/targets/copy/tasks/selinux.yml
+test/integration/targets/copy/tasks/setgid.yml
 test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir.yml
 
test/integration/targets/copy/tasks/src_file_dest_file_in_non_existent_dir_remote_src.yml
 test/integration/targets/copy/tasks/src_remote_file_is_not_file.yml
@@ -2209,6 +2218,7 @@
 test/integration/targets/include_import/test_loop_var_bleed.yaml
 test/integration/targets/include_import/test_nested_tasks.yml
 test/integration/targets/include_import/test_nested_tasks_fqcn.yml
+test/integration/targets/include_import/test_null_include_filename.yml
 test/integration/targets/include_import/test_role_recursion.yml
 test/integration/targets/include_import/test_role_recursion_fqcn.yml
 test/integration/targets/include_import/apply/import_apply.yml
@@ -2233,6 +2243,7 @@
 test/integration/targets/include_import/include_role_omit/playbook.yml
 
test/integration/targets/include_import/include_role_omit/roles/foo/tasks/main.yml
 test/integration/targets/include_import/nestedtasks/nested/nested.yml
+test/integration/targets/include_import/null_filename/tasks.yml
 test/integration/targets/include_import/parent_templating/playbook.yml
 
test/integration/targets/include_import/parent_templating/roles/test/tasks/localhost.yml
 
test/integration/targets/include_import/parent_templating/roles/test/tasks/main.yml
@@ -2617,6 +2628,9 @@
 test/integration/targets/lookup_first_found/files/bar1
 test/integration/targets/lookup_first_found/files/foo1
 test/integration/targets/lookup_first_found/files/vars file spaces.yml
+test/integration/targets/lookup_first_found/roles/a/tasks/main.yml
+test/integration/targets/lookup_first_found/roles/a/tasks/subdir/main.yml
+test/integration/targets/lookup_first_found/roles/a/tasks/subdir/relative
 test/integration/targets/lookup_first_found/tasks/main.yml
 test/integration/targets/lookup_first_found/vars/ishouldnotbefound.yml
 test/integration/targets/lookup_first_found/vars/itworks.yml
@@ -3451,6 +3465,11 @@
 test/integration/targets/strategy_free/last_include_tasks.yml
 test/integration/targets/strategy_free/runme.sh
 test/integration/targets/strategy_free/test_last_include_in_always.yml
+test/integration/targets/strategy_host_pinned/aliases
+test/integration/targets/strategy_host_pinned/hosts
+test/integration/targets/strategy_host_pinned/playbook.yml
+test/integration/targets/strategy_host_pinned/runme.sh
+test/integration/targets/strategy_host_pinned/callback_plugins/callback_host_count.py
 test/integration/targets/strategy_linear/aliases
 test/integration/targets/strategy_linear/inventory
 test/integration/targets/strategy_linear/runme.sh
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/packaging/release.py 
new/ansible_core-2.17.4/packaging/release.py
--- old/ansible_core-2.17.3/packaging/release.py        2024-08-12 
23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/packaging/release.py        2024-09-09 
21:26:24.000000000 +0200
@@ -369,6 +369,7 @@
 ANSIBLE_BIN_DIR = CHECKOUT_DIR / "bin"
 ANSIBLE_RELEASE_FILE = ANSIBLE_DIR / "release.py"
 ANSIBLE_REQUIREMENTS_FILE = CHECKOUT_DIR / "requirements.txt"
+ANSIBLE_PYPROJECT_TOML_FILE = CHECKOUT_DIR / "pyproject.toml"
 
 DIST_DIR = CHECKOUT_DIR / "dist"
 VENV_DIR = DIST_DIR / ".venv" / "release"
@@ -708,6 +709,35 @@
     return env
 
 
+def get_pypi_project(repository: str, project: str, version: Version | None = 
None) -> dict[str, t.Any]:
+    """Return the project JSON from PyPI for the specified repository, project 
and version (optional)."""
+    endpoint = PYPI_ENDPOINTS[repository]
+
+    if version:
+        url = f"{endpoint}/{project}/{version}/json"
+    else:
+        url = f"{endpoint}/{project}/json"
+
+    opener = urllib.request.build_opener()
+    response: http.client.HTTPResponse
+
+    try:
+        with opener.open(url) as response:
+            data = json.load(response)
+    except urllib.error.HTTPError as ex:
+        if version:
+            target = f'{project!r} version {version}'
+        else:
+            target = f'{project!r}'
+
+        if ex.status == http.HTTPStatus.NOT_FOUND:
+            raise ApplicationError(f"Could not find {target} on PyPI.") from 
None
+
+        raise RuntimeError(f"Failed to get {target} from PyPI.") from ex
+
+    return data
+
+
 def get_ansible_version(version: str | None = None, /, commit: str | None = 
None, mode: VersionMode = VersionMode.DEFAULT) -> Version:
     """Parse and return the current ansible-core version, the provided version 
or the version from the provided commit."""
     if version and commit:
@@ -802,6 +832,38 @@
     ANSIBLE_RELEASE_FILE.write_text(updated)
 
 
+def get_latest_setuptools_version() -> Version:
+    """Return the latest setuptools version found on PyPI."""
+    data = get_pypi_project('pypi', 'setuptools')
+    version = Version(data['info']['version'])
+
+    return version
+
+
+def set_setuptools_upper_bound(requested_version: Version) -> None:
+    """Set the upper bound on setuptools in pyproject.toml."""
+    current = ANSIBLE_PYPROJECT_TOML_FILE.read_text()
+    pattern = re.compile(r'^(?P<begin>requires = \["setuptools >= 
)(?P<lower>[^,]+)(?P<middle>, <= )(?P<upper>[^"]+)(?P<end>".*)$', re.MULTILINE)
+    match = pattern.search(current)
+
+    if not match:
+        raise ApplicationError(f"Unable to find the 'requires' entry in: 
{ANSIBLE_PYPROJECT_TOML_FILE.relative_to(CHECKOUT_DIR)}")
+
+    current_version = Version(match.group('upper'))
+
+    if requested_version == current_version:
+        return
+
+    display.show(f"Updating setuptools upper bound from {current_version} to 
{requested_version} ...")
+
+    updated = 
pattern.sub(fr'\g<begin>\g<lower>\g<middle>{requested_version}\g<end>', current)
+
+    if current == updated:
+        raise RuntimeError("Failed to set the setuptools upper bound.")
+
+    ANSIBLE_PYPROJECT_TOML_FILE.write_text(updated)
+
+
 def create_reproducible_sdist(original_path: pathlib.Path, output_path: 
pathlib.Path, mtime: int) -> None:
     """Read the specified sdist and write out a new copy with uniform file 
metadata at the specified location."""
     with tarfile.open(original_path) as original_archive:
@@ -878,21 +940,7 @@
 @functools.cache
 def get_release_artifact_details(repository: str, version: Version, validate: 
bool) -> list[ReleaseArtifact]:
     """Return information about the release artifacts hosted on PyPI."""
-    endpoint = PYPI_ENDPOINTS[repository]
-    url = f"{endpoint}/ansible-core/{version}/json"
-
-    opener = urllib.request.build_opener()
-    response: http.client.HTTPResponse
-
-    try:
-        with opener.open(url) as response:
-            data = json.load(response)
-    except urllib.error.HTTPError as ex:
-        if ex.status == http.HTTPStatus.NOT_FOUND:
-            raise ApplicationError(f"Version {version} not found on PyPI.") 
from None
-
-        raise RuntimeError(f"Failed to get {version} from PyPI: {ex}") from ex
-
+    data = get_pypi_project(repository, 'ansible-core', version)
     artifacts = [describe_release_artifact(version, item, validate) for item 
in data["urls"]]
 
     expected_artifact_types = {"bdist_wheel", "sdist"}
@@ -1138,6 +1186,7 @@
     mailto=dict(name="--mailto", action="store_true", help="write announcement 
to mailto link instead of console"),
     validate=dict(name="--no-validate", action="store_false", help="disable 
validation of PyPI artifacts against local ones"),
     prompt=dict(name="--no-prompt", action="store_false", help="disable 
interactive prompt before publishing with twine"),
+    setuptools=dict(name='--no-setuptools', action="store_false", 
help="disable updating setuptools upper bound"),
     allow_tag=dict(action="store_true", help="allow an existing release tag 
(for testing)"),
     allow_stale=dict(action="store_true", help="allow a stale checkout (for 
testing)"),
     allow_dirty=dict(action="store_true", help="allow untracked files and 
files with changes (for testing)"),
@@ -1194,10 +1243,11 @@
 
 # noinspection PyUnusedLocal
 @command
-def prepare(final: bool = False, pre: str | None = None, version: str | None = 
None) -> None:
+def prepare(final: bool = False, pre: str | None = None, version: str | None = 
None, setuptools: bool | None = None) -> None:
     """Prepare a release."""
     command.run(
         update_version,
+        update_setuptools,
         check_state,
         generate_summary,
         generate_changelog,
@@ -1219,6 +1269,16 @@
 
 
 @command
+def update_setuptools(setuptools: bool) -> None:
+    """Update the setuptools upper bound in pyproject.toml."""
+    if not setuptools:
+        return
+
+    requested_version = get_latest_setuptools_version()
+    set_setuptools_upper_bound(requested_version)
+
+
+@command
 def generate_summary() -> None:
     """Generate a summary changelog fragment for this release."""
     version = get_ansible_version()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ansible_core-2.17.3/pyproject.toml 
new/ansible_core-2.17.4/pyproject.toml
--- old/ansible_core-2.17.3/pyproject.toml      2024-08-12 23:17:08.000000000 
+0200
+++ new/ansible_core-2.17.4/pyproject.toml      2024-09-09 21:26:24.000000000 
+0200
@@ -1,3 +1,3 @@
 [build-system]
-requires = ["setuptools >= 66.1.0"]  # minimum setuptools version supporting 
Python 3.12
+requires = ["setuptools >= 66.1.0, <= 72.1.0"]  # lower bound to support 
controller Python versions, upper bound for latest version tested at release
 build-backend = "setuptools.build_meta"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/aliases
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/aliases
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/aliases
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/aliases
       2024-09-09 21:26:24.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.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/galaxy.yml
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/galaxy.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/galaxy.yml
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/galaxy.yml
  2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,5 @@
+namespace: ns
+name: no_version
+version: null
+authors:
+  - Ansible
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/meta/runtime.yml
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/meta/runtime.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/meta/runtime.yml
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/no_version/meta/runtime.yml
    2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,11 @@
+extra_key: true
+plugin_routing:
+  modules:
+    deprecated_module:
+      deprecation:
+        removal_version: 2.0.0
+        warning_text: Will no longer be there.
+    tombstoned_module:
+      tombstone:
+        removal_version: 1.0.0
+        warning_text: Is no longer there.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/galaxy.yml
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/galaxy.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/galaxy.yml
     1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/galaxy.yml
     2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,5 @@
+namespace: ns
+name: version
+version: 2.3.4
+authors:
+  - Ansible
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/meta/runtime.yml
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/meta/runtime.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/meta/runtime.yml
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/ansible_collections/ns/version/meta/runtime.yml
       2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,18 @@
+plugin_routing:
+  modules:
+    deprecated_module:
+      deprecation:
+        removal_version: 3.0.0
+        warning_text: Will no longer be there.
+    tombstoned_module:
+      tombstone:
+        removal_version: 2.0.0
+        warning_text: Is no longer there.
+    deprecated_module_wrong_version:
+      deprecation:
+        removal_version: 2.0.0
+        warning_text: Will no longer be there.
+    tombstoned_module_wrong_version:
+      tombstone:
+        removal_version: 3.0.0
+        warning_text: Is no longer there.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-no_version.txt
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-no_version.txt
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-no_version.txt
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-no_version.txt
       2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1 @@
+meta/runtime.yml:0:0: extra keys not allowed @ data['extra_key']. Got True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-version.txt
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-version.txt
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-version.txt
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/expected-version.txt
  2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,2 @@
+meta/runtime.yml:0:0: The deprecation removal_version ('2.0.0') must be after 
the current version (SemanticVersion('2.3.4')) for dictionary value @ 
data['plugin_routing']['modules']['deprecated_module_wrong_version']['deprecation']['removal_version'].
 Got '2.0.0'
+meta/runtime.yml:0:0: The tombstone removal_version ('3.0.0') must not be 
after the current version (SemanticVersion('2.3.4')) for dictionary value @ 
data['plugin_routing']['modules']['tombstoned_module_wrong_version']['tombstone']['removal_version'].
 Got '3.0.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/runme.sh
 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/runme.sh
--- 
old/ansible_core-2.17.3/test/integration/targets/ansible-test-sanity-runtime-metadata/runme.sh
      1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/ansible-test-sanity-runtime-metadata/runme.sh
      2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+COLLECTION_NAME=version source ../collection/setup.sh
+
+set -eux
+
+cd ../version
+ansible-test sanity --test runtime-metadata --color --truncate 0 --failure-ok 
--lint "${@}" 1> actual-stdout.txt 2> actual-stderr.txt
+diff -u "${TEST_DIR}/expected-version.txt" actual-stdout.txt
+grep -F -f "${TEST_DIR}/expected-version.txt" actual-stderr.txt
+
+cd ../no_version
+ansible-test sanity --test runtime-metadata --color --truncate 0 --failure-ok 
--lint "${@}" 1> actual-stdout.txt 2> actual-stderr.txt
+diff -u "${TEST_DIR}/expected-no_version.txt" actual-stdout.txt
+grep -F -f "${TEST_DIR}/expected-no_version.txt" actual-stderr.txt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/ansible-vault/runme.sh 
new/ansible_core-2.17.4/test/integration/targets/ansible-vault/runme.sh
--- old/ansible_core-2.17.3/test/integration/targets/ansible-vault/runme.sh     
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/ansible-vault/runme.sh     
2024-09-09 21:26:24.000000000 +0200
@@ -546,21 +546,22 @@
 ansible-vault encrypt_string content
 ansible-vault encrypt_string content --encrypt-vault-id id3
 
-set +e
-
 # Try to use a missing vault password file
-ansible-vault encrypt_string content --encrypt-vault-id id1 2>&1 | tee out.txt
-test $? -ne 0
-grep out.txt -e '[WARNING]: Error getting vault password file (id1)'
-grep out.txt -e "ERROR! Did not find a match for --encrypt-vault-id=id2 in the 
known vault-ids ['id3']"
+if ansible-vault encrypt_string content --encrypt-vault-id id1 > out.txt 2>&1; 
then
+  echo "command did not fail"
+  exit 1
+fi
+grep out.txt -e '\[WARNING\]: Error getting vault password file (id1)'
+grep out.txt -e "ERROR! Did not find a match for --encrypt-vault-id=id1 in the 
known vault-ids \['id3'\]"
 
 # Try to use an inaccessible vault password file
-ansible-vault encrypt_string content --encrypt-vault-id id2 2>&1 | tee out.txt
-test $? -ne 0
-grep out.txt -e "[WARNING]: Error in vault password file loading (id2)"
-grep out.txt -e "ERROR! Did not find a match for --encrypt-vault-id=id2 in the 
known vault-ids ['id3']"
+if ansible-vault encrypt_string content --encrypt-vault-id id2 > out.txt 2>&1; 
then
+  echo "command did not fail"
+  exit 1
+fi
+grep out.txt -e "\[WARNING\]: Error in vault password file loading (id2)"
+grep out.txt -e "ERROR! Did not find a match for --encrypt-vault-id=id2 in the 
known vault-ids \['id3'\]"
 
-set -e
 unset ANSIBLE_VAULT_IDENTITY_LIST
 
 # 'real script'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/collection/setup.sh 
new/ansible_core-2.17.4/test/integration/targets/collection/setup.sh
--- old/ansible_core-2.17.3/test/integration/targets/collection/setup.sh        
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/collection/setup.sh        
2024-09-09 21:26:24.000000000 +0200
@@ -24,6 +24,6 @@
 trap 'rm -rf "${WORK_DIR}"' EXIT
 
 cp -a "${TEST_DIR}/ansible_collections" "${WORK_DIR}"
-cd "${WORK_DIR}/ansible_collections/ns/col"
+cd "${WORK_DIR}/ansible_collections/ns/${COLLECTION_NAME:-col}"
 
 "${TEST_DIR}/../collection/update-ignore.py"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/connection/test_connection.yml 
new/ansible_core-2.17.4/test/integration/targets/connection/test_connection.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/connection/test_connection.yml 
    2024-08-12 23:17:08.000000000 +0200
+++ 
new/ansible_core-2.17.4/test/integration/targets/connection/test_connection.yml 
    2024-09-09 21:26:24.000000000 +0200
@@ -3,6 +3,25 @@
   serial: 1
   tasks:
 
+  # SSH with scp has troubles with using complex filenames that require quoting
+  # or escaping. The more complex filename scenario is skipped in this mode.
+  # The default of sftp has no problems with these filenames.
+  - name: check if ssh with the scp file transfer is being tested
+    set_fact:
+      skip_complex_filename: >-
+        {{
+            ansible_connection == "ssh" and
+            lookup("ansible.builtin.config",
+                "ssh_transfer_method",
+                plugin_name=ansible_connection,
+                plugin_type="connection",
+            ) == "scp"
+        }}
+
+  - name: set test filename
+    set_fact:
+      test_filename: 汉语-{{ skip_complex_filename | ternary("file", "['foo 
bar']") }}.txt
+
   ### raw with unicode arg and output
 
   - name: raw with unicode arg and output
@@ -17,20 +36,20 @@
   ### copy local file with unicode filename and content
 
   - name: create local file with unicode filename and content
-    local_action: lineinfile dest={{ local_tmp }}-汉语/汉语.txt 
create=true line=汉语
+    local_action: lineinfile dest={{ local_tmp }}-汉语/{{ test_filename }} 
create=true line=汉语
   - name: remove remote file with unicode filename and content
-    action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语/汉语.txt 
state=absent"
+    action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语/{{ 
test_filename }} state=absent"
   - name: create remote directory with unicode name
     action: "{{ action_prefix }}file path={{ remote_tmp }}-汉语 
state=directory"
   - name: copy local file with unicode filename and content
-    action: "{{ action_prefix }}copy src={{ local_tmp }}-汉语/汉语.txt 
dest={{ remote_tmp }}-汉语/汉语.txt"
+    action: "{{ action_prefix }}copy src={{ local_tmp }}-汉语/{{ 
test_filename }} dest={{ remote_tmp }}-汉语/{{ test_filename }}"
 
   ### fetch remote file with unicode filename and content
 
   - name: remove local file with unicode filename and content
-    local_action: file path={{ local_tmp }}-汉语/汉语.txt state=absent
+    local_action: file path={{ local_tmp }}-汉语/{{ test_filename }} 
state=absent
   - name: fetch remote file with unicode filename and content
-    fetch: src={{ remote_tmp }}-汉语/汉语.txt dest={{ local_tmp 
}}-汉语/汉语.txt fail_on_missing=true validate_checksum=true flat=true
+    fetch: src={{ remote_tmp }}-汉语/{{ test_filename }} dest={{ local_tmp 
}}-汉语/{{ test_filename }} fail_on_missing=true validate_checksum=true 
flat=true
 
   ### remove local and remote temp files
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/connection_psrp/tests.yml 
new/ansible_core-2.17.4/test/integration/targets/connection_psrp/tests.yml
--- old/ansible_core-2.17.3/test/integration/targets/connection_psrp/tests.yml  
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/connection_psrp/tests.yml  
2024-09-09 21:26:24.000000000 +0200
@@ -126,3 +126,16 @@
         path: /tmp/empty.txt
         state: absent
       delegate_to: localhost
+
+  - name: Test PSRP HTTP connection
+    win_ping:
+    vars:
+      ansible_port: 5985
+      ansible_psrp_protocol: http
+
+  - name: Test PSRP HTTPS connection
+    win_ping:
+    vars:
+      ansible_port: 5986
+      ansible_psrp_protocol: https
+      ansible_psrp_cert_validation: ignore
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/connection_winrm/tests.yml 
new/ansible_core-2.17.4/test/integration/targets/connection_winrm/tests.yml
--- old/ansible_core-2.17.3/test/integration/targets/connection_winrm/tests.yml 
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/connection_winrm/tests.yml 
2024-09-09 21:26:24.000000000 +0200
@@ -41,3 +41,26 @@
   - assert:
       that:
       - timeout_cmd.msg == 'The win_shell action failed to execute in the 
expected time frame (5) and was terminated'
+
+  - name: Test WinRM HTTP connection
+    win_ping:
+    vars:
+      ansible_port: 5985
+      ansible_winrm_scheme: http
+      ansible_winrm_transport: ntlm  # Verifies message encryption over HTTP
+
+  - name: Test WinRM HTTPS connection
+    win_ping:
+    vars:
+      ansible_port: 5986
+      ansible_winrm_scheme: https
+      ansible_winrm_server_cert_validation: ignore
+
+  - name: emit raw CLIXML on stderr with special chars
+    raw: $host.UI.WriteErrorLine("Test 🎵 _x005F_ _x005Z_.")
+    register: stderr_clixml
+
+  - name: assert emit raw CLIXML on stderr with special chars
+    assert:
+      that:
+      - stderr_clixml.stderr_lines == ['Test 🎵 _x005F_ _x005Z_.']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/copy/defaults/main.yml 
new/ansible_core-2.17.4/test/integration/targets/copy/defaults/main.yml
--- old/ansible_core-2.17.3/test/integration/targets/copy/defaults/main.yml     
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/copy/defaults/main.yml     
2024-09-09 21:26:24.000000000 +0200
@@ -1,2 +1,3 @@
 ---
 remote_unprivileged_user: tmp_ansible_test_user
+remote_unprivileged_user_group: test_ansible_test_group
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/copy/tasks/main.yml 
new/ansible_core-2.17.4/test/integration/targets/copy/tasks/main.yml
--- old/ansible_core-2.17.3/test/integration/targets/copy/tasks/main.yml        
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/copy/tasks/main.yml        
2024-09-09 21:26:24.000000000 +0200
@@ -29,9 +29,15 @@
       with_dict: "{{ symlinks }}"
       delegate_to: localhost
 
+    - name: Create group for remote unprivileged user
+      group:
+        name: '{{ remote_unprivileged_user_group }}'
+      register: group
+
     - name: Create remote unprivileged remote user
       user:
         name: '{{ remote_unprivileged_user }}'
+        group: '{{ remote_unprivileged_user_group }}'
       register: user
 
     - name: Check sudoers dir
@@ -78,6 +84,8 @@
     - import_tasks: selinux.yml
       when: ansible_os_family == 'RedHat' and ansible_selinux.get('mode') == 
'enforcing'
 
+    - import_tasks: setgid.yml
+
     - import_tasks: no_log.yml
       delegate_to: localhost
 
@@ -122,6 +130,11 @@
         remove: yes
         force: yes
 
+    - name: Remove group for remote unprivileged user
+      group:
+        name: '{{ remote_unprivileged_user_group }}'
+        state: absent
+
     - name: Remove sudoers.d file
       file:
         path: "{{ sudoers_d_file }}"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/copy/tasks/setgid.yml 
new/ansible_core-2.17.4/test/integration/targets/copy/tasks/setgid.yml
--- old/ansible_core-2.17.3/test/integration/targets/copy/tasks/setgid.yml      
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.17.4/test/integration/targets/copy/tasks/setgid.yml      
2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,27 @@
+- block:
+    - name: Create test directory
+      file:
+        path: "{{ remote_tmp_dir }}/test_setgid"
+        state: directory
+        mode: '2750'
+        recurse: yes
+        owner: '{{ remote_unprivileged_user }}'
+        group: '{{ remote_unprivileged_user_group }}'
+
+    - name: Test creating a file respects setgid on parent dir
+      copy:
+        content: |
+          test file
+        dest: "{{ remote_tmp_dir }}/test_setgid/test.txt"
+
+    - stat:
+        path: "{{ remote_tmp_dir }}/test_setgid/test.txt"
+      register: result
+
+    - assert:
+        that:
+          - result.stat.gr_name == remote_unprivileged_user_group
+  always:
+    - file:
+        path: "{{ remote_tmp_dir }}/test_setgid"
+        state: absent
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/include_import/null_filename/tasks.yml
 
new/ansible_core-2.17.4/test/integration/targets/include_import/null_filename/tasks.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/include_import/null_filename/tasks.yml
     1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/include_import/null_filename/tasks.yml
     2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,5 @@
+- name: ping task
+  ansible.builtin.ping:
+
+- name: invalid include_task definition
+  ansible.builtin.include_tasks:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/include_import/runme.sh 
new/ansible_core-2.17.4/test/integration/targets/include_import/runme.sh
--- old/ansible_core-2.17.3/test/integration/targets/include_import/runme.sh    
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/include_import/runme.sh    
2024-09-09 21:26:24.000000000 +0200
@@ -148,3 +148,9 @@
 # https://github.com/ansible/ansible/issues/73657
 ansible-playbook issue73657.yml 2>&1 | tee issue73657.out
 test "$(grep -c 'SHOULD_NOT_EXECUTE' issue73657.out)" = 0
+
+# https://github.com/ansible/ansible/issues/83874
+ansible-playbook test_null_include_filename.yml 2>&1 | tee 
test_null_include_filename.out
+test "$(grep -c 'ERROR! No file specified for ansible.builtin.include_tasks' 
test_null_include_filename.out)" = 1
+test "$(grep -c 'The error appears to be in 
'\''.*/include_import/null_filename/tasks.yml'\'': line 4, column 3.*' 
test_null_include_filename.out)" = 1
+test "$(grep -c '\- name: invalid include_task definition' 
test_null_include_filename.out)" = 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/include_import/test_null_include_filename.yml
 
new/ansible_core-2.17.4/test/integration/targets/include_import/test_null_include_filename.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/include_import/test_null_include_filename.yml
      1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/include_import/test_null_include_filename.yml
      2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,7 @@
+- name: Test include failure with invalid included include_task
+  hosts: localhost
+  gather_facts: false
+
+  tasks:
+  - ansible.builtin.include_tasks:
+      file: null_filename/tasks.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/lookup_csvfile/tasks/main.yml 
new/ansible_core-2.17.4/test/integration/targets/lookup_csvfile/tasks/main.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/lookup_csvfile/tasks/main.yml  
    2024-08-12 23:17:08.000000000 +0200
+++ 
new/ansible_core-2.17.4/test/integration/targets/lookup_csvfile/tasks/main.yml  
    2024-09-09 21:26:24.000000000 +0200
@@ -4,6 +4,12 @@
   ignore_errors: yes
   register: no_keyword
 
+- name: using modern syntax but missing keyword
+  set_fact:
+    this_will_error: "{{ lookup('csvfile', file='people.csv', delimiter=' ', 
col=1) }}"
+  ignore_errors: yes
+  register: modern_no_keyword
+
 - name: extra arg in k=v syntax (deprecated)
   set_fact:
     this_will_error: "{{ lookup('csvfile', 'foo file=people.csv delimiter=, 
col=1 thisarg=doesnotexist') }}"
@@ -27,6 +33,9 @@
       - no_keyword is failed
       - >
         "Search key is required but was not found" in no_keyword.msg
+      - modern_no_keyword is failed
+      - >
+        "Search key is required but was not found" in modern_no_keyword.msg
       - invalid_arg is failed
       - invalid_arg2 is failed
       - >
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/roles/a/tasks/main.yml
 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/roles/a/tasks/main.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/roles/a/tasks/main.yml
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/roles/a/tasks/main.yml
  2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1 @@
+- include_tasks: subdir/main.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/roles/a/tasks/subdir/main.yml
 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/roles/a/tasks/subdir/main.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/roles/a/tasks/subdir/main.yml
   1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/roles/a/tasks/subdir/main.yml
   2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,7 @@
+- assert:
+    that:
+      - "lookup('first_found', task_adjacent) is is_file"
+  vars:
+    task_adjacent:
+      - files:
+          - relative
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/tasks/main.yml
 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/tasks/main.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/lookup_first_found/tasks/main.yml
  2024-08-12 23:17:08.000000000 +0200
+++ 
new/ansible_core-2.17.4/test/integration/targets/lookup_first_found/tasks/main.yml
  2024-09-09 21:26:24.000000000 +0200
@@ -152,3 +152,7 @@
   assert:
     that:
       - q('first_found', ['/nonexistant'], skip=True) == []
+
+- name: Test relative paths in roles
+  include_role:
+    role: "{{ role_path }}/roles/a"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/loops/tasks/main.yml 
new/ansible_core-2.17.4/test/integration/targets/loops/tasks/main.yml
--- old/ansible_core-2.17.3/test/integration/targets/loops/tasks/main.yml       
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/loops/tasks/main.yml       
2024-09-09 21:26:24.000000000 +0200
@@ -405,3 +405,20 @@
 - assert:
     that:
       - foo[0] == 'foo1.0'
+
+# https://github.com/ansible/ansible/issues/83619
+- command: "echo {{ item }}"
+  register: r
+  loop:
+    - changed
+    - skipped
+  loop_control:
+    label: "{{ r.stdout }}"
+  when:
+    - item == "changed"
+  ignore_errors: true
+
+- name: test that the second iteration result was stored, since it was skipped 
no stdout should be present there
+  assert:
+    that:
+      - "r['results'][1]['msg'] is contains(\"no attribute 'stdout'\")"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/aliases 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/aliases
--- 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/aliases   
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/aliases   
    2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1 @@
+shippable/posix/group5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/callback_plugins/callback_host_count.py
 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/callback_plugins/callback_host_count.py
--- 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/callback_plugins/callback_host_count.py
       1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/callback_plugins/callback_host_count.py
       2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,43 @@
+# (c) 2024 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or 
https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import annotations
+
+from ansible.plugins.callback import CallbackBase
+
+
+class CallbackModule(CallbackBase):
+    CALLBACK_VERSION = 2.0
+    CALLBACK_TYPE = 'stdout'
+    CALLBACK_NAME = 'callback_host_count'
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._executing_hosts_counter = 0
+
+    def v2_playbook_on_task_start(self, task, is_conditional):
+        self._display.display(task.name or task.action)
+
+        if task.name == "start":
+            self._executing_hosts_counter += 1
+
+        # NOTE assumes 2 forks
+        num_forks = 2
+        if self._executing_hosts_counter > num_forks:
+            # Exception is caught and turned into just a warning in TQM,
+            # so raise BaseException to fail the test
+            # To prevent seeing false positives in case the exception handling
+            # in TQM is changed and BaseException is swallowed, print something
+            # and ensure the test fails in runme.sh in such a case.
+            self._display.display("host_pinned_test_failed")
+            raise BaseException(
+                "host_pinned test failed, number of hosts executing: "
+                f"{self._executing_hosts_counter}, expected: {num_forks}"
+            )
+
+    def v2_playbook_on_handler_task_start(self, task):
+        self._display.display(task.name or task.action)
+
+    def v2_runner_on_ok(self, result):
+        if result._task.name == "end":
+            self._executing_hosts_counter -= 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/hosts 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/hosts
--- old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/hosts 
1970-01-01 01:00:00.000000000 +0100
+++ new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/hosts 
2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,14 @@
+localhost0
+localhost1
+localhost2
+localhost3
+localhost4
+localhost5
+localhost6
+localhost7
+localhost8
+localhost9
+
+[all:vars]
+ansible_connection=local
+ansible_python_interpreter={{ansible_playbook_python}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/playbook.yml
 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/playbook.yml
--- 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/playbook.yml
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/playbook.yml
  2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,46 @@
+# README - even the name of the tasks matter in this test, see 
callback_plugins/callback_host_count.py
+- hosts: all
+  gather_facts: false
+  strategy: host_pinned
+  pre_tasks:
+    # by executing in pre_tasks we ensure that "start" is the first task in 
the play,
+    # not an implicit "meta: flush_handlers" after pre_tasks
+    - name: start
+      debug:
+        msg: start
+
+    - ping:
+
+    - meta: noop
+  post_tasks:
+    # notifying a handler in post_tasks ensures the handler is the last task 
in the play,
+    # not an implicit "meta: flush_handlers" after post_tasks
+    - debug:
+      changed_when: true
+      notify: end
+  handlers:
+    - name: end
+      debug:
+        msg: end
+
+- hosts: localhost0,localhost1,localhost2
+  gather_facts: false
+  strategy: host_pinned
+  pre_tasks:
+    - name: start
+      debug:
+        msg: start
+
+    - command: sleep 3
+      when: inventory_hostname == "localhost0"
+
+    - meta: noop
+    - meta: noop
+  post_tasks:
+    - debug:
+      changed_when: true
+      notify: end
+  handlers:
+    - name: end
+      debug:
+        msg: end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/runme.sh 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/runme.sh
--- 
old/ansible_core-2.17.3/test/integration/targets/strategy_host_pinned/runme.sh  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/ansible_core-2.17.4/test/integration/targets/strategy_host_pinned/runme.sh  
    2024-09-09 21:26:24.000000000 +0200
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+set -o pipefail
+
+export ANSIBLE_STDOUT_CALLBACK=callback_host_count
+
+# the number of forks matter, see callback_plugins/callback_host_count.py
+ansible-playbook --inventory hosts --forks 2 playbook.yml | tee 
"${OUTPUT_DIR}/out.txt"
+
+[ "$(grep -c 'host_pinned_test_failed' "${OUTPUT_DIR}/out.txt")" -eq 0 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/integration/targets/win_fetch/aliases 
new/ansible_core-2.17.4/test/integration/targets/win_fetch/aliases
--- old/ansible_core-2.17.3/test/integration/targets/win_fetch/aliases  
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/integration/targets/win_fetch/aliases  
2024-09-09 21:26:24.000000000 +0200
@@ -1 +1,2 @@
 shippable/windows/group1
+shippable/windows/smoketest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
 
new/ansible_core-2.17.4/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
--- 
old/ansible_core-2.17.3/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
        2024-08-12 23:17:08.000000000 +0200
+++ 
new/ansible_core-2.17.4/test/lib/ansible_test/_util/controller/sanity/code-smell/runtime-metadata.py
        2024-09-09 21:26:24.000000000 +0200
@@ -123,7 +123,9 @@
     # noinspection PyBroadException
     try:
         result = collection_detail.read_manifest_json('.') or 
collection_detail.read_galaxy_yml('.')
-        return SemanticVersion(result['version'])
+        version = SemanticVersion()
+        version.parse(result['version'])
+        return version
     except Exception:  # pylint: disable=broad-except
         # We do not care why it fails, in case we cannot get the version
         # just return None to indicate "we don't know".
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/sanity/code-smell/package-data.py 
new/ansible_core-2.17.4/test/sanity/code-smell/package-data.py
--- old/ansible_core-2.17.3/test/sanity/code-smell/package-data.py      
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/sanity/code-smell/package-data.py      
2024-09-09 21:26:24.000000000 +0200
@@ -95,7 +95,7 @@
 def build(source_dir: str, tmp_dir: str) -> tuple[pathlib.Path, pathlib.Path]:
     """Create a sdist and wheel."""
     create = subprocess.run(
-        [sys.executable, '-m', 'build', '--no-isolation', '--outdir', tmp_dir],
+        [sys.executable, '-m', 'build', '--outdir', tmp_dir],
         stdin=subprocess.DEVNULL,
         capture_output=True,
         text=True,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/units/plugins/connection/test_psrp.py 
new/ansible_core-2.17.4/test/units/plugins/connection/test_psrp.py
--- old/ansible_core-2.17.3/test/units/plugins/connection/test_psrp.py  
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/units/plugins/connection/test_psrp.py  
2024-09-09 21:26:24.000000000 +0200
@@ -220,12 +220,13 @@
         pc = PlayContext()
         new_stdin = StringIO()
 
-        conn = connection_loader.get('psrp', pc, new_stdin)
-        conn.set_options(var_options={'_extras': {'ansible_psrp_mock_test3': 
True}})
+        for conn_name in ('psrp', 'ansible.legacy.psrp'):
+            conn = connection_loader.get(conn_name, pc, new_stdin)
+            conn.set_options(var_options={'_extras': 
{'ansible_psrp_mock_test3': True}})
 
-        mock_display = MagicMock()
-        monkeypatch.setattr(Display, "warning", mock_display)
-        conn._build_kwargs()
+            mock_display = MagicMock()
+            monkeypatch.setattr(Display, "warning", mock_display)
+            conn._build_kwargs()
 
-        assert mock_display.call_args[0][0] == \
-            'ansible_psrp_mock_test3 is unsupported by the current psrp 
version installed'
+            assert mock_display.call_args[0][0] == \
+                'ansible_psrp_mock_test3 is unsupported by the current psrp 
version installed'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/ansible_core-2.17.3/test/units/plugins/shell/test_powershell.py 
new/ansible_core-2.17.4/test/units/plugins/shell/test_powershell.py
--- old/ansible_core-2.17.3/test/units/plugins/shell/test_powershell.py 
2024-08-12 23:17:08.000000000 +0200
+++ new/ansible_core-2.17.4/test/units/plugins/shell/test_powershell.py 
2024-09-09 21:26:24.000000000 +0200
@@ -1,5 +1,7 @@
 from __future__ import annotations
 
+import pytest
+
 from ansible.plugins.shell.powershell import _parse_clixml, ShellModule
 
 
@@ -27,7 +29,8 @@
                     b'<S S="Error">At line:1 char:1_x000D__x000A_</S>' \
                     b'<S S="Error">+ fake cmdlet_x000D__x000A_</S><S 
S="Error">+ ~~~~_x000D__x000A_</S>' \
                     b'<S S="Error">    + CategoryInfo          : 
ObjectNotFound: (fake:String) [], CommandNotFoundException_x000D__x000A_</S>' \
-                    b'<S S="Error">    + FullyQualifiedErrorId : 
CommandNotFoundException_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S>' \
+                    b'<S S="Error">    + FullyQualifiedErrorId : 
CommandNotFoundException_x000D__x000A_</S>' \
+                    b'<S S="Error"> _x000D__x000A_</S>' \
                     b'</Objs>'
     expected = b"fake : The term 'fake' is not recognized as the name of a 
cmdlet. Check \r\n" \
                b"the spelling of the name, or if a path was included.\r\n" \
@@ -35,7 +38,8 @@
                b"+ fake cmdlet\r\n" \
                b"+ ~~~~\r\n" \
                b"    + CategoryInfo          : ObjectNotFound: (fake:String) 
[], CommandNotFoundException\r\n" \
-               b"    + FullyQualifiedErrorId : CommandNotFoundException\r\n "
+               b"    + FullyQualifiedErrorId : CommandNotFoundException\r\n" \
+               b" \r\n"
     actual = _parse_clixml(single_stream)
     assert actual == expected
 
@@ -49,8 +53,9 @@
                       b'<S S="Error">    + CategoryInfo          : 
ObjectNotFound: (fake:String) [], CommandNotFoundException_x000D__x000A_</S>' \
                       b'<S S="Error">    + FullyQualifiedErrorId : 
CommandNotFoundException_x000D__x000A_</S><S S="Error"> _x000D__x000A_</S>' \
                       b'<S S="Info">hi info</S>' \
+                      b'<S S="Info">other</S>' \
                       b'</Objs>'
-    expected = b"hi info"
+    expected = b"hi infoother"
     actual = _parse_clixml(multiple_stream, stream="Info")
     assert actual == expected
 
@@ -74,6 +79,32 @@
     assert actual == expected
 
 
+@pytest.mark.parametrize('clixml, expected', [
+    ('', ''),
+    ('just newline _x000A_', 'just newline \n'),
+    ('surrogate pair _xD83C__xDFB5_', 'surrogate pair 🎵'),
+    ('null char _x0000_', 'null char \0'),
+    ('normal char _x0061_', 'normal char a'),
+    ('escaped literal _x005F_x005F_', 'escaped literal _x005F_'),
+    ('underscope before escape _x005F__x000A_', 'underscope before escape 
_\n'),
+    ('surrogate high _xD83C_', 'surrogate high \uD83C'),
+    ('surrogate low _xDFB5_', 'surrogate low \uDFB5'),
+    ('lower case hex _x005f_', 'lower case hex _'),
+    ('invalid hex _x005G_', 'invalid hex _x005G_'),
+])
+def test_parse_clixml_with_comlex_escaped_chars(clixml, expected):
+    clixml_data = (
+        '<# CLIXML\r\n'
+        '<Objs Version="1.1.0.1" 
xmlns="http://schemas.microsoft.com/powershell/2004/04";>'
+        f'<S S="Error">{clixml}</S>'
+        '</Objs>'
+    ).encode()
+    b_expected = expected.encode(errors="surrogatepass")
+
+    actual = _parse_clixml(clixml_data)
+    assert actual == b_expected
+
+
 def test_join_path_unc():
     pwsh = ShellModule()
     unc_path_parts = ['\\\\host\\share\\dir1\\\\dir2\\', '\\dir3/dir4', 
'dir5', 'dir6\\']

++++++ ansible_core-2.17.3.tar.gz.sha256 -> ansible_core-2.17.4.tar.gz.sha256 
++++++
--- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.17.3.tar.gz.sha256   
2024-08-15 09:58:44.568011275 +0200
+++ 
/work/SRC/openSUSE:Factory/.ansible-core.new.29891/ansible_core-2.17.4.tar.gz.sha256
        2024-09-16 17:40:50.419508643 +0200
@@ -1 +1 @@
-917557065339fe36e7078e9bea47eefab6d6877f3bd435fa5f0d766d04c58485  
ansible_core-2.17.3.tar.gz
+44a1f30076796536ba2455cad18d36e62870f04e632e3ca2ebe970d7beacf24d  
ansible_core-2.17.4.tar.gz

Reply via email to