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-12-05 17:08:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core (Old) and /work/SRC/openSUSE:Factory/.ansible-core.new.28523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core" Thu Dec 5 17:08:32 2024 rev:37 rq:1228282 version:2.18.1 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes 2024-12-02 16:58:56.708724839 +0100 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.28523/ansible-core.changes 2024-12-05 17:10:06.049261770 +0100 @@ -1,0 +2,27 @@ +Tue Dec 3 05:53:19 UTC 2024 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- update to 2.18.1: + * Minor Changes + - ansible-test - When detection of the current container + network fails, a warning is now issued and execution + continues. This simplifies usage in cases where the current + container cannot be inspected, such as when running in GitHub + Codespaces. + * Security Fixes + - Templating will not prefer AnsibleUnsafe when a variable is + referenced via hostvars - CVE-2024-11079 + * Bugfixes + - Fix returning 'unreachable' for the overall task result. This + prevents false positives when a looped task has unignored + unreachable items (#84019). + - ansible-test - Fix traceback that occurs after an interactive + command fails. + - dnf5 - fix installing a package using state=latest when a + binary of the same name as the package is already installed + (#84259) + - dnf5 - matching on a binary can be achieved only by + specifying a full path (#84334) + - runas become - Fix up become logic to still get the SYSTEM + token with the most privileges when running as SYSTEM. + +------------------------------------------------------------------- Old: ---- ansible_core-2.18.0.tar.gz ansible_core-2.18.0.tar.gz.sha256 New: ---- ansible_core-2.18.1.tar.gz ansible_core-2.18.1.tar.gz.sha256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core.spec ++++++ --- /var/tmp/diff_new_pack.4zM3mT/_old 2024-12-05 17:10:06.925298456 +0100 +++ /var/tmp/diff_new_pack.4zM3mT/_new 2024-12-05 17:10:06.925298456 +0100 @@ -43,7 +43,7 @@ %endif Name: ansible-core -Version: 2.18.0 +Version: 2.18.1 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later ++++++ ansible_core-2.18.0.tar.gz -> ansible_core-2.18.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/PKG-INFO new/ansible_core-2.18.1/PKG-INFO --- old/ansible_core-2.18.0/PKG-INFO 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/PKG-INFO 2024-12-02 18:46:46.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.18.0 +Version: 2.18.1 Summary: Radically simple IT automation Author: Ansible Project Project-URL: Homepage, https://ansible.com/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/ansible_core.egg-info/PKG-INFO new/ansible_core-2.18.1/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.18.0/ansible_core.egg-info/PKG-INFO 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/ansible_core.egg-info/PKG-INFO 2024-12-02 18:46:46.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ansible-core -Version: 2.18.0 +Version: 2.18.1 Summary: Radically simple IT automation Author: Ansible Project Project-URL: Homepage, https://ansible.com/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.18.1/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.18.0/ansible_core.egg-info/SOURCES.txt 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/ansible_core.egg-info/SOURCES.txt 2024-12-02 18:46:46.000000000 +0100 @@ -2216,6 +2216,7 @@ test/integration/targets/ignore_unreachable/inventory test/integration/targets/ignore_unreachable/runme.sh test/integration/targets/ignore_unreachable/test_base_cannot_connect.yml +test/integration/targets/ignore_unreachable/test_base_loop_cannot_connect.yml test/integration/targets/ignore_unreachable/test_cannot_connect.yml test/integration/targets/ignore_unreachable/test_with_bad_plugins.yml test/integration/targets/ignore_unreachable/fake_connectors/bad_exec.py @@ -3594,6 +3595,7 @@ test/integration/targets/template/badnull3.cfg test/integration/targets/template/corner_cases.yml test/integration/targets/template/custom_template.yml +test/integration/targets/template/cve-2024-11079.yml test/integration/targets/template/filter_plugins.yml test/integration/targets/template/in_template_overrides.j2 test/integration/targets/template/lazy_eval.yml @@ -4442,6 +4444,8 @@ test/units/ansible_test/__init__.py test/units/ansible_test/conftest.py test/units/ansible_test/test_diff.py +test/units/ansible_test/_internal/__init__.py +test/units/ansible_test/_internal/test_util.py test/units/ansible_test/ci/__init__.py test/units/ansible_test/ci/test_azp.py test/units/ansible_test/ci/util.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/changelogs/CHANGELOG-v2.18.rst new/ansible_core-2.18.1/changelogs/CHANGELOG-v2.18.rst --- old/ansible_core-2.18.0/changelogs/CHANGELOG-v2.18.rst 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/changelogs/CHANGELOG-v2.18.rst 2024-12-02 18:46:46.000000000 +0100 @@ -4,6 +4,34 @@ .. contents:: Topics +v2.18.1 +======= + +Release Summary +--------------- + +| Release Date: 2024-12-02 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.18/porting_guides/porting_guide_core_2.18.html>`__ + +Minor Changes +------------- + +- ansible-test - When detection of the current container network fails, a warning is now issued and execution continues. This simplifies usage in cases where the current container cannot be inspected, such as when running in GitHub Codespaces. + +Security Fixes +-------------- + +- Templating will not prefer AnsibleUnsafe when a variable is referenced via hostvars - CVE-2024-11079 + +Bugfixes +-------- + +- Fix returning 'unreachable' for the overall task result. This prevents false positives when a looped task has unignored unreachable items (https://github.com/ansible/ansible/issues/84019). +- ansible-test - Fix traceback that occurs after an interactive command fails. +- dnf5 - fix installing a package using ``state=latest`` when a binary of the same name as the package is already installed (https://github.com/ansible/ansible/issues/84259) +- dnf5 - matching on a binary can be achieved only by specifying a full path (https://github.com/ansible/ansible/issues/84334) +- runas become - Fix up become logic to still get the SYSTEM token with the most privileges when running as SYSTEM. + v2.18.0 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/changelogs/changelog.yaml new/ansible_core-2.18.1/changelogs/changelog.yaml --- old/ansible_core-2.18.0/changelogs/changelog.yaml 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/changelogs/changelog.yaml 2024-12-02 18:46:46.000000000 +0100 @@ -612,3 +612,50 @@ - cve-2024-8775.yml - user_ssh_fix.yml release_date: '2024-10-29' + 2.18.1: + changes: + release_summary: '| Release Date: 2024-12-02 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.18/porting_guides/porting_guide_core_2.18.html>`__ + + ' + codename: Fool in the Rain + fragments: + - 2.18.1_summary.yaml + release_date: '2024-12-02' + 2.18.1rc1: + changes: + bugfixes: + - Fix returning 'unreachable' for the overall task result. This prevents false + positives when a looped task has unignored unreachable items (https://github.com/ansible/ansible/issues/84019). + - ansible-test - Fix traceback that occurs after an interactive command fails. + - dnf5 - fix installing a package using ``state=latest`` when a binary of the + same name as the package is already installed (https://github.com/ansible/ansible/issues/84259) + - dnf5 - matching on a binary can be achieved only by specifying a full path + (https://github.com/ansible/ansible/issues/84334) + - runas become - Fix up become logic to still get the SYSTEM token with the + most privileges when running as SYSTEM. + minor_changes: + - ansible-test - When detection of the current container network fails, a warning + is now issued and execution continues. This simplifies usage in cases where + the current container cannot be inspected, such as when running in GitHub + Codespaces. + release_summary: '| Release Date: 2024-11-25 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.18/porting_guides/porting_guide_core_2.18.html>`__ + + ' + security_fixes: + - Templating will not prefer AnsibleUnsafe when a variable is referenced via + hostvars - CVE-2024-11079 + codename: Fool in the Rain + fragments: + - 2.18.1rc1_summary.yaml + - 84019-ignore_unreachable-loop.yml + - 84259-dnf5-latest-fix.yml + - 84334-dnf5-consolidate-settings.yml + - ansible-test-fix-command-traceback.yml + - ansible-test-network-detection.yml + - become-runas-system-deux.yml + - unsafe_hostvars_fix.yml + release_date: '2024-11-25' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/executor/task_executor.py new/ansible_core-2.18.1/lib/ansible/executor/task_executor.py --- old/ansible_core-2.18.0/lib/ansible/executor/task_executor.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/executor/task_executor.py 2024-12-02 18:46:46.000000000 +0100 @@ -150,6 +150,7 @@ if 'unreachable' in item and item['unreachable']: item_ignore_unreachable = item.pop('_ansible_ignore_unreachable') if not res.get('unreachable'): + res['unreachable'] = True self._task.ignore_unreachable = item_ignore_unreachable elif self._task.ignore_unreachable and not item_ignore_unreachable: self._task.ignore_unreachable = item_ignore_unreachable diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.18.1/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.18.0/lib/ansible/module_utils/ansible_release.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/module_utils/ansible_release.py 2024-12-02 18:46:46.000000000 +0100 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.18.0' +__version__ = '2.18.1' __author__ = 'Ansible, Inc.' __codename__ = "Fool in the Rain" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs new/ansible_core-2.18.1/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs --- old/ansible_core-2.18.0/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/module_utils/csharp/Ansible.AccessToken.cs 2024-12-02 18:46:46.000000000 +0100 @@ -339,19 +339,47 @@ public static IEnumerable<SafeNativeHandle> EnumerateUserTokens(SecurityIdentifier sid, TokenAccessLevels access = TokenAccessLevels.Query) { + return EnumerateUserTokens(sid, access, (p, h) => true); + } + + public static IEnumerable<SafeNativeHandle> EnumerateUserTokens( + SecurityIdentifier sid, + TokenAccessLevels access, + Func<System.Diagnostics.Process, SafeNativeHandle, bool> processFilter) + { + // We always need the Query access level so we can query the TokenUser + access |= TokenAccessLevels.Query; + foreach (System.Diagnostics.Process process in System.Diagnostics.Process.GetProcesses()) { - // We always need the Query access level so we can query the TokenUser using (process) - using (SafeNativeHandle hToken = TryOpenAccessToken(process, access | TokenAccessLevels.Query)) + using (SafeNativeHandle processHandle = NativeMethods.OpenProcess(ProcessAccessFlags.QueryInformation, false, (UInt32)process.Id)) { - if (hToken == null) + if (processHandle.IsInvalid) + { continue; + } - if (!sid.Equals(GetTokenUser(hToken))) + if (!processFilter(process, processHandle)) + { continue; + } + + SafeNativeHandle accessToken; + if (!NativeMethods.OpenProcessToken(processHandle, access, out accessToken)) + { + continue; + } + + using (accessToken) + { + if (!sid.Equals(GetTokenUser(accessToken))) + { + continue; + } - yield return hToken; + yield return accessToken; + } } } } @@ -440,18 +468,5 @@ for (int i = 0; i < array.Length; i++, ptrOffset = IntPtr.Add(ptrOffset, Marshal.SizeOf(typeof(T)))) array[i] = (T)Marshal.PtrToStructure(ptrOffset, typeof(T)); } - - private static SafeNativeHandle TryOpenAccessToken(System.Diagnostics.Process process, TokenAccessLevels access) - { - try - { - using (SafeNativeHandle hProcess = OpenProcess(process.Id, ProcessAccessFlags.QueryInformation, false)) - return OpenProcessToken(hProcess, access); - } - catch (Win32Exception) - { - return null; - } - } } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/module_utils/csharp/Ansible.Become.cs new/ansible_core-2.18.1/lib/ansible/module_utils/csharp/Ansible.Become.cs --- old/ansible_core-2.18.0/lib/ansible/module_utils/csharp/Ansible.Become.cs 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/module_utils/csharp/Ansible.Become.cs 2024-12-02 18:46:46.000000000 +0100 @@ -93,10 +93,21 @@ CachedRemoteInteractive, CachedUnlock } + + [Flags] + public enum ProcessChildProcessPolicyFlags + { + None = 0x0, + NoChildProcessCreation = 0x1, + AuditNoChildProcessCreation = 0x2, + AllowSecureProcessCreation = 0x4, + } } internal class NativeMethods { + public const int ProcessChildProcessPolicy = 13; + [DllImport("advapi32.dll", SetLastError = true)] public static extern bool AllocateLocallyUniqueId( out Luid Luid); @@ -116,6 +127,13 @@ [DllImport("kernel32.dll")] public static extern UInt32 GetCurrentThreadId(); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern bool GetProcessMitigationPolicy( + SafeNativeHandle hProcess, + int MitigationPolicy, + ref NativeHelpers.ProcessChildProcessPolicyFlags lpBuffer, + IntPtr dwLength); + [DllImport("user32.dll", SetLastError = true)] public static extern NoopSafeHandle GetProcessWindowStation(); @@ -217,6 +235,7 @@ }; private static int WINDOWS_STATION_ALL_ACCESS = 0x000F037F; private static int DESKTOP_RIGHTS_ALL_ACCESS = 0x000F01FF; + private static bool _getProcessMitigationPolicySupported = true; public static Result CreateProcessAsUser(string username, string password, string command) { @@ -333,12 +352,13 @@ // Grant access to the current Windows Station and Desktop to the become user GrantAccessToWindowStationAndDesktop(account); - // Try and impersonate a SYSTEM token. We need the SeTcbPrivilege for - // - LogonUser for a service SID - // - S4U logon - // - Token elevation + // Try and impersonate a SYSTEM token, we need a SYSTEM token to either become a well known service + // account or have administrative rights on the become access token. + // If we ultimately are becoming the SYSTEM account we want the token with the most privileges available. + // https://github.com/ansible/ansible/issues/71453 + bool usedForProcess = becomeSid == "S-1-5-18"; systemToken = GetPrimaryTokenForUser(new SecurityIdentifier("S-1-5-18"), - new List<string>() { "SeTcbPrivilege" }); + new List<string>() { "SeTcbPrivilege" }, usedForProcess); if (systemToken != null) { try @@ -356,9 +376,11 @@ try { + if (becomeSid == "S-1-5-18") + userTokens.Add(systemToken); // Cannot use String.IsEmptyOrNull() as an empty string is an account that doesn't have a pass. // We only use S4U if no password was defined or it was null - if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials) + else if (!SERVICE_SIDS.Contains(becomeSid) && password == null && logonType != LogonType.NewCredentials) { // If no password was specified, try and duplicate an existing token for that user or use S4U to // generate one without network credentials @@ -381,11 +403,6 @@ string domain = null; switch (becomeSid) { - case "S-1-5-18": - logonType = LogonType.Service; - domain = "NT AUTHORITY"; - username = "SYSTEM"; - break; case "S-1-5-19": logonType = LogonType.Service; domain = "NT AUTHORITY"; @@ -427,8 +444,10 @@ return userTokens; } - private static SafeNativeHandle GetPrimaryTokenForUser(SecurityIdentifier sid, - List<string> requiredPrivileges = null) + private static SafeNativeHandle GetPrimaryTokenForUser( + SecurityIdentifier sid, + List<string> requiredPrivileges = null, + bool usedForProcess = false) { // According to CreateProcessWithTokenW we require a token with // TOKEN_QUERY, TOKEN_DUPLICATE and TOKEN_ASSIGN_PRIMARY @@ -438,7 +457,19 @@ TokenAccessLevels.AssignPrimary | TokenAccessLevels.Impersonate; - foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess)) + SafeNativeHandle userToken = null; + int privilegeCount = 0; + + // If we are using this token for the process, we need to check the + // process mitigation policy allows child processes to be created. + var processFilter = usedForProcess + ? (Func<System.Diagnostics.Process, SafeNativeHandle, bool>)((p, t) => + { + return GetProcessChildProcessPolicyFlags(t) == NativeHelpers.ProcessChildProcessPolicyFlags.None; + }) + : ((p, t) => true); + + foreach (SafeNativeHandle hToken in TokenUtil.EnumerateUserTokens(sid, dwAccess, processFilter)) { // Filter out any Network logon tokens, using become with that is useless when S4U // can give us a Batch logon @@ -448,6 +479,10 @@ List<string> actualPrivileges = TokenUtil.GetTokenPrivileges(hToken).Select(x => x.Name).ToList(); + // If the token has less or the same number of privileges than the current token, skip it. + if (usedForProcess && privilegeCount >= actualPrivileges.Count) + continue; + // Check that the required privileges are on the token if (requiredPrivileges != null) { @@ -459,16 +494,22 @@ // Duplicate the token to convert it to a primary token with the access level required. try { - return TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, + userToken = TokenUtil.DuplicateToken(hToken, TokenAccessLevels.MaximumAllowed, SecurityImpersonationLevel.Anonymous, TokenType.Primary); + privilegeCount = actualPrivileges.Count; } catch (Process.Win32Exception) { continue; } + + // If we don't care about getting the token with the most privileges, escape the loop as we already + // have a token. + if (!usedForProcess) + break; } - return null; + return userToken; } private static SafeNativeHandle GetS4UTokenForUser(SecurityIdentifier sid, LogonType logonType) @@ -581,6 +622,35 @@ return null; } + private static NativeHelpers.ProcessChildProcessPolicyFlags GetProcessChildProcessPolicyFlags(SafeNativeHandle processHandle) + { + // Because this is only used to check the policy, we ignore any + // errors and pretend that the policy is None. + NativeHelpers.ProcessChildProcessPolicyFlags policy = NativeHelpers.ProcessChildProcessPolicyFlags.None; + + if (_getProcessMitigationPolicySupported) + { + try + { + if (NativeMethods.GetProcessMitigationPolicy( + processHandle, + NativeMethods.ProcessChildProcessPolicy, + ref policy, + (IntPtr)4)) + { + return policy; + } + } + catch (EntryPointNotFoundException) + { + // If the function is not available, we won't try to call it again + _getProcessMitigationPolicySupported = false; + } + } + + return policy; + } + private static NativeHelpers.SECURITY_LOGON_TYPE GetTokenLogonType(SafeNativeHandle hToken) { TokenStatistics stats = TokenUtil.GetTokenStatistics(hToken); @@ -637,4 +707,4 @@ { } } } -} +} \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/modules/command.py new/ansible_core-2.18.1/lib/ansible/modules/command.py --- old/ansible_core-2.18.0/lib/ansible/modules/command.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/modules/command.py 2024-12-02 18:46:46.000000000 +0100 @@ -15,12 +15,11 @@ description: - The M(ansible.builtin.command) module takes the command name followed by a list of space-delimited arguments. - The given command will be executed on all selected nodes. - - The command(s) will not be - processed through the shell, so variables like C($HOSTNAME) and operations - like C("*"), C("<"), C(">"), C("|"), C(";") and C("&") will not work. + - The command(s) will not be processed through the shell, so operations like C("*"), C("<"), C(">"), C("|"), C(";") and C("&") will not work. + Also, environment variables are resolved via Python, not shell, see O(expand_argument_vars) and are left unchanged if not matched. Use the M(ansible.builtin.shell) module if you need these features. - - To create C(command) tasks that are easier to read than the ones using space-delimited - arguments, pass parameters using the C(args) L(task keyword,https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task) + - To create C(command) tasks that are easier to read than the ones using space-delimited arguments, + pass parameters using the C(args) L(task keyword,https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html#task) or use O(cmd) parameter. - Either a free form command or O(cmd) parameter is required, see the examples. - For Windows targets, use the M(ansible.windows.win_command) module instead. @@ -41,8 +40,8 @@ options: expand_argument_vars: description: - - Expands the arguments that are variables, for example C($HOME) will be expanded before being passed to the - command to run. + - Expands the arguments that are variables, for example C($HOME) will be expanded before being passed to the command to run. + - If a variable is not matched, it is left unchanged, unlike shell substitution which would remove it. - Set to V(false) to disable expansion and treat the value as a literal argument. type: bool default: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/modules/dnf5.py new/ansible_core-2.18.1/lib/ansible/modules/dnf5.py --- old/ansible_core-2.18.0/lib/ansible/modules/dnf5.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/modules/dnf5.py 2024-12-02 18:46:46.000000000 +0100 @@ -358,6 +358,21 @@ def is_installed(base, spec): settings = libdnf5.base.ResolveSpecSettings() + try: + settings.set_group_with_name(True) + # Disable checking whether SPEC is a binary -> `/usr/(s)bin/<SPEC>`, + # this prevents scenarios like the following: + # * the `sssd-common` package is installed and provides `/usr/sbin/sssd` binary + # * the `sssd` package is NOT installed + # * due to `set_with_binaries(True)` being default `is_installed(base, "sssd")` would "unexpectedly" return True + # If users wish to target the `sssd` binary they can by specifying the full path `name=/usr/sbin/sssd` explicitly + # due to settings.set_with_filenames(True) being default. + settings.set_with_binaries(False) + except AttributeError: + # dnf5 < 5.2.0.0 + settings.group_with_name = True + settings.with_binaries = False + installed_query = libdnf5.rpm.PackageQuery(base) installed_query.filter_installed() match, nevra = installed_query.resolve_pkg_spec(spec, settings, True) @@ -646,9 +661,12 @@ settings = libdnf5.base.GoalJobSettings() try: settings.set_group_with_name(True) + settings.set_with_binaries(False) except AttributeError: # dnf5 < 5.2.0.0 settings.group_with_name = True + settings.with_binaries = False + if self.bugfix or self.security: advisory_query = libdnf5.advisory.AdvisoryQuery(base) types = [] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/plugins/lookup/varnames.py new/ansible_core-2.18.1/lib/ansible/plugins/lookup/varnames.py --- old/ansible_core-2.18.0/lib/ansible/plugins/lookup/varnames.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/plugins/lookup/varnames.py 2024-12-02 18:46:46.000000000 +0100 @@ -13,11 +13,14 @@ _terms: description: List of Python regex patterns to search for in variable names. required: True + seealso: + - plugin_type: lookup + plugin: ansible.builtin.vars """ EXAMPLES = """ - name: List variables that start with qz_ - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '^qz_.+')}}" + ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '^qz_.+') }}" vars: qz_1: hello qz_2: world @@ -25,13 +28,16 @@ qz_: "I won't show either" - name: Show all variables - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+')}}" + ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+') }}" - name: Show variables with 'hosts' in their names - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', 'hosts')}}" + ansible.builtin.debug: msg="{{ q('varnames', 'hosts') }}" - name: Find several related variables that end specific way - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.varnames', '.+_zone$', '.+_location$') }}" + ansible.builtin.debug: msg="{{ query('ansible.builtin.varnames', '.+_zone$', '.+_location$') }}" + +- name: display values from variables found via varnames (note "*" is used to dereference the list to a 'list of arguments') + debug: msg="{{ lookup('vars', *lookup('varnames', 'ansible_play_.+')) }}" """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/plugins/lookup/vars.py new/ansible_core-2.18.1/lib/ansible/plugins/lookup/vars.py --- old/ansible_core-2.18.0/lib/ansible/plugins/lookup/vars.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/plugins/lookup/vars.py 2024-12-02 18:46:46.000000000 +0100 @@ -17,6 +17,10 @@ description: - What to return if a variable is undefined. - If no default is set, it will result in an error if any of the variables is undefined. + seealso: + - plugin_type: lookup + plugin: ansible.builtin.varnames + """ EXAMPLES = """ @@ -27,20 +31,23 @@ myvar: ename - name: Show default empty since i dont have 'variablnotename' - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar, default='')}}" + ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar, default='') }}" vars: variablename: hello myvar: notename - name: Produce an error since i dont have 'variablnotename' - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar)}}" + ansible.builtin.debug: msg="{{ q('vars', 'variabl' + myvar) }}" ignore_errors: True vars: variablename: hello myvar: notename - name: find several related variables - ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'ansible_play_hosts', 'ansible_play_batch', 'ansible_play_hosts_all') }}" + ansible.builtin.debug: msg="{{ query('ansible.builtin.vars', 'ansible_play_hosts', 'ansible_play_batch', 'ansible_play_hosts_all') }}" + +- name: show values from variables found via varnames (note "*" is used to dereference the list to a 'list of arguments') + debug: msg="{{ q('vars', *q('varnames', 'ansible_play_.+')) }}" - name: Access nested variables ansible.builtin.debug: msg="{{ lookup('ansible.builtin.vars', 'variabl' + myvar).sub_var }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/release.py new/ansible_core-2.18.1/lib/ansible/release.py --- old/ansible_core-2.18.0/lib/ansible/release.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/release.py 2024-12-02 18:46:46.000000000 +0100 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.18.0' +__version__ = '2.18.1' __author__ = 'Ansible, Inc.' __codename__ = "Fool in the Rain" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/template/__init__.py new/ansible_core-2.18.1/lib/ansible/template/__init__.py --- old/ansible_core-2.18.0/lib/ansible/template/__init__.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/template/__init__.py 2024-12-02 18:46:46.000000000 +0100 @@ -48,7 +48,7 @@ from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes from ansible.module_utils.common.collections import is_sequence from ansible.plugins.loader import filter_loader, lookup_loader, test_loader -from ansible.template.native_helpers import ansible_native_concat, ansible_eval_concat, ansible_concat +from ansible.template.native_helpers import AnsibleUndefined, ansible_native_concat, ansible_eval_concat, ansible_concat from ansible.template.template import AnsibleJ2Template from ansible.template.vars import AnsibleJ2Vars from ansible.utils.display import Display @@ -312,35 +312,6 @@ return functools.update_wrapper(wrapper, func) -class AnsibleUndefined(StrictUndefined): - ''' - A custom Undefined class, which returns further Undefined objects on access, - rather than throwing an exception. - ''' - def __getattr__(self, name): - if name == '__UNSAFE__': - # AnsibleUndefined should never be assumed to be unsafe - # This prevents ``hasattr(val, '__UNSAFE__')`` from evaluating to ``True`` - raise AttributeError(name) - # Return original Undefined object to preserve the first failure context - return self - - def __getitem__(self, key): - # Return original Undefined object to preserve the first failure context - return self - - def __repr__(self): - return 'AnsibleUndefined(hint={0!r}, obj={1!r}, name={2!r})'.format( - self._undefined_hint, - self._undefined_obj, - self._undefined_name - ) - - def __contains__(self, item): - # Return original Undefined object to preserve the first failure context - return self - - class AnsibleContext(Context): ''' A custom context, which intercepts resolve_or_missing() calls and sets a flag diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/template/native_helpers.py new/ansible_core-2.18.1/lib/ansible/template/native_helpers.py --- old/ansible_core-2.18.0/lib/ansible/template/native_helpers.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/template/native_helpers.py 2024-12-02 18:46:46.000000000 +0100 @@ -5,13 +5,19 @@ import ast +from collections.abc import Mapping from itertools import islice, chain from types import GeneratorType +from ansible.module_utils.common.collections import is_sequence from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.six import string_types from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode from ansible.utils.native_jinja import NativeJinjaText +from ansible.utils.unsafe_proxy import wrap_var +import ansible.module_utils.compat.typing as t + +from jinja2.runtime import StrictUndefined _JSON_MAP = { @@ -28,6 +34,40 @@ return ast.Constant(value=_JSON_MAP[node.id]) +def _is_unsafe(value: t.Any) -> bool: + """ + Our helper function, which will also recursively check dict and + list entries due to the fact that they may be repr'd and contain + a key or value which contains jinja2 syntax and would otherwise + lose the AnsibleUnsafe value. + """ + to_check = [value] + seen = set() + + while True: + if not to_check: + break + + val = to_check.pop(0) + val_id = id(val) + + if val_id in seen: + continue + seen.add(val_id) + + if isinstance(val, AnsibleUndefined): + continue + if isinstance(val, Mapping): + to_check.extend(val.keys()) + to_check.extend(val.values()) + elif is_sequence(val): + to_check.extend(val) + elif getattr(val, '__UNSAFE__', False): + return True + + return False + + def ansible_eval_concat(nodes): """Return a string of concatenated compiled nodes. Throw an undefined error if any of the nodes is undefined. @@ -43,17 +83,28 @@ if not head: return '' + unsafe = False + if len(head) == 1: out = head[0] if isinstance(out, NativeJinjaText): return out + unsafe = _is_unsafe(out) out = to_text(out) else: if isinstance(nodes, GeneratorType): nodes = chain(head, nodes) - out = ''.join([to_text(v) for v in nodes]) + + out_values = [] + for v in nodes: + if not unsafe and _is_unsafe(v): + unsafe = True + + out_values.append(to_text(v)) + + out = ''.join(out_values) # if this looks like a dictionary, list or bool, convert it to such if out.startswith(('{', '[')) or out in ('True', 'False'): @@ -68,6 +119,9 @@ except (TypeError, ValueError, SyntaxError, MemoryError): pass + if unsafe: + out = wrap_var(out) + return out @@ -78,7 +132,19 @@ Used in Templar.template() when jinja2_native=False and convert_data=False. """ - return ''.join([to_text(v) for v in nodes]) + unsafe = False + values = [] + for v in nodes: + if not unsafe and _is_unsafe(v): + unsafe = True + + values.append(to_text(v)) + + out = ''.join(values) + if unsafe: + out = wrap_var(out) + + return out def ansible_native_concat(nodes): @@ -95,6 +161,8 @@ if not head: return None + unsafe = False + if len(head) == 1: out = head[0] @@ -115,10 +183,21 @@ # short-circuit literal_eval for anything other than strings if not isinstance(out, string_types): return out + + unsafe = _is_unsafe(out) + else: if isinstance(nodes, GeneratorType): nodes = chain(head, nodes) - out = ''.join([to_text(v) for v in nodes]) + + out_values = [] + for v in nodes: + if not unsafe and _is_unsafe(v): + unsafe = True + + out_values.append(to_text(v)) + + out = ''.join(out_values) try: evaled = ast.literal_eval( @@ -128,10 +207,45 @@ ast.parse(out, mode='eval') ) except (TypeError, ValueError, SyntaxError, MemoryError): + if unsafe: + out = wrap_var(out) + return out if isinstance(evaled, string_types): quote = out[0] - return f'{quote}{evaled}{quote}' + evaled = f'{quote}{evaled}{quote}' + + if unsafe: + evaled = wrap_var(evaled) return evaled + + +class AnsibleUndefined(StrictUndefined): + """ + A custom Undefined class, which returns further Undefined objects on access, + rather than throwing an exception. + """ + def __getattr__(self, name): + if name == '__UNSAFE__': + # AnsibleUndefined should never be assumed to be unsafe + # This prevents ``hasattr(val, '__UNSAFE__')`` from evaluating to ``True`` + raise AttributeError(name) + # Return original Undefined object to preserve the first failure context + return self + + def __getitem__(self, key): + # Return original Undefined object to preserve the first failure context + return self + + def __repr__(self): + return 'AnsibleUndefined(hint={0!r}, obj={1!r}, name={2!r})'.format( + self._undefined_hint, + self._undefined_obj, + self._undefined_name + ) + + def __contains__(self, item): + # Return original Undefined object to preserve the first failure context + return self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/lib/ansible/vars/hostvars.py new/ansible_core-2.18.1/lib/ansible/vars/hostvars.py --- old/ansible_core-2.18.0/lib/ansible/vars/hostvars.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/lib/ansible/vars/hostvars.py 2024-12-02 18:46:46.000000000 +0100 @@ -92,10 +92,12 @@ return self._find_host(host_name) is not None def __iter__(self): - yield from self._inventory.hosts + # include implicit localhost only if it has variables set + yield from self._inventory.hosts | {'localhost': self._inventory.localhost} if self._inventory.localhost else {} def __len__(self): - return len(self._inventory.hosts) + # include implicit localhost only if it has variables set + return len(self._inventory.hosts) + (1 if self._inventory.localhost else 0) def __repr__(self): out = {} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/pyproject.toml new/ansible_core-2.18.1/pyproject.toml --- old/ansible_core-2.18.0/pyproject.toml 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/pyproject.toml 2024-12-02 18:46:46.000000000 +0100 @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools >= 66.1.0, <= 75.3.0"] # lower bound to support controller Python versions, upper bound for latest version tested at release +requires = ["setuptools >= 66.1.0, <= 75.6.0"] # lower bound to support controller Python versions, 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.18.0/test/integration/targets/dnf/tasks/repo.yml new/ansible_core-2.18.1/test/integration/targets/dnf/tasks/repo.yml --- old/ansible_core-2.18.0/test/integration/targets/dnf/tasks/repo.yml 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/dnf/tasks/repo.yml 2024-12-02 18:46:46.000000000 +0100 @@ -517,3 +517,73 @@ dnf: name: provides_foo* state: absent + +# https://github.com/ansible/ansible/issues/84259 +- name: test installing a package named `package-name` while a package providing `/usr/sbin/package-name` is installed + block: + - dnf: + name: package-name + state: absent + + - dnf: + name: provides-binary + state: present + + - dnf: + name: package-name + state: latest + register: dnf_result + + - assert: + that: + - dnf_result is changed + always: + - name: Clean up + dnf: + name: + - provides-binary + - package-name + state: absent + +- name: test installing a package that provides a binary by specifying the binary name + block: + - dnf: + name: provides-binary + state: absent + + - dnf: + name: /usr/sbin/package-name + state: present + register: dnf_result + + - assert: + that: + - dnf_result is changed + always: + - name: Clean up + dnf: + name: provides-binary + state: absent + +# https://github.com/ansible/ansible/issues/84334 +- name: test that a binary is not matched by its base name + block: + - dnf: + name: provides-binary + state: present + + - dnf: + name: package-name + state: absent + register: dnf_result + + - assert: + that: + - dnf_result is not changed + always: + - name: Clean up + dnf: + name: + - provides-binary + - package-name + state: absent diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/integration/targets/ignore_unreachable/runme.sh new/ansible_core-2.18.1/test/integration/targets/ignore_unreachable/runme.sh --- old/ansible_core-2.18.0/test/integration/targets/ignore_unreachable/runme.sh 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/ignore_unreachable/runme.sh 2024-12-02 18:46:46.000000000 +0100 @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -eux +export ANSIBLE_TIMEOUT=1 + export ANSIBLE_CONNECTION_PLUGINS=./fake_connectors # use fake connectors that raise errors at different stages ansible-playbook test_with_bad_plugins.yml -i inventory -v "$@" @@ -14,3 +16,9 @@ else echo "Connection to nonexistent hosts failed without using ignore_unreachable. Success!" fi + +if ansible-playbook test_base_loop_cannot_connect.yml -i inventory -v "$@" > out.txt; then + echo "Playbook intended to fail succeeded. Connection succeeded to nonexistent host" + exit 1 +fi +grep out.txt -e 'ignored=1' | grep 'unreachable=2' | grep 'ok=1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/integration/targets/ignore_unreachable/test_base_loop_cannot_connect.yml new/ansible_core-2.18.1/test/integration/targets/ignore_unreachable/test_base_loop_cannot_connect.yml --- old/ansible_core-2.18.0/test/integration/targets/ignore_unreachable/test_base_loop_cannot_connect.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/ignore_unreachable/test_base_loop_cannot_connect.yml 2024-12-02 18:46:46.000000000 +0100 @@ -0,0 +1,41 @@ +- hosts: localhost,nonexistent + gather_facts: false + tasks: + - name: Test ignore_unreachable for all items (pass) + ping: + ignore_unreachable: "{{ item.ignore_unreachable }}" + loop: + - ignore_unreachable: true + - ignore_unreachable: true + register: unreachable_ignored + + - name: Test ignore_unreachable for second item (fail) + ping: + ignore_unreachable: "{{ item.ignore_unreachable }}" + loop: + - ignore_unreachable: false + - ignore_unreachable: true + register: unreachable_first + + - meta: clear_host_errors + + - name: Test ignore_unreachable for first item (fail) + ping: + ignore_unreachable: "{{ item.ignore_unreachable }}" + loop: + - ignore_unreachable: true + - ignore_unreachable: false + register: unreachable_last + + - meta: clear_host_errors + + - assert: + that: + - unreachable_ignored is not unreachable + - unreachable_first["results"][0] is unreachable + - unreachable_first["results"][-1] is not unreachable + - unreachable_first is unreachable + - unreachable_last["results"][-1] is unreachable + - unreachable_last["results"][0] is not unreachable + - unreachable_last is unreachable + when: inventory_hostname == 'nonexistent' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/integration/targets/setup_rpm_repo/library/create_repo.py new/ansible_core-2.18.1/test/integration/targets/setup_rpm_repo/library/create_repo.py --- old/ansible_core-2.18.0/test/integration/targets/setup_rpm_repo/library/create_repo.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/setup_rpm_repo/library/create_repo.py 2024-12-02 18:46:46.000000000 +0100 @@ -15,8 +15,10 @@ from rpmfluff.make import make_gif from rpmfluff.sourcefile import GeneratedSourceFile from rpmfluff.rpmbuild import SimpleRpmBuild + from rpmfluff.utils import expectedArch from rpmfluff.yumrepobuild import YumRepoBuild except ImportError: + expectedArch = None # define here to avoid NameError as it is used on top level in SPECS HAS_RPMFLUFF = False @@ -30,6 +32,7 @@ recommends: list[str] | None = None requires: list[str] | None = None file: str | None = None + binary: str | None = None SPECS = [ @@ -58,6 +61,8 @@ RPM(name='broken-b', version='1.0', requires=['broken-a = 1.2.3-1']), RPM(name='broken-c', version='1.0', requires=['broken-c = 1.2.4-1']), RPM(name='broken-d', version='1.0', requires=['broken-a']), + RPM(name='provides-binary', version='1.0', arch=[expectedArch], binary='/usr/sbin/package-name'), + RPM(name='package-name', version='1.0'), ] @@ -81,10 +86,13 @@ ) ) + if spec.binary: + pkg.add_simple_compilation(installPath=spec.binary) + pkgs.append(pkg) repo = YumRepoBuild(pkgs) - repo.make('noarch', 'i686', 'x86_64') + repo.make('noarch', 'i686', 'x86_64', expectedArch) for pkg in pkgs: pkg.clean() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/integration/targets/template/cve-2024-11079.yml new/ansible_core-2.18.1/test/integration/targets/template/cve-2024-11079.yml --- old/ansible_core-2.18.0/test/integration/targets/template/cve-2024-11079.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/template/cve-2024-11079.yml 2024-12-02 18:46:46.000000000 +0100 @@ -0,0 +1,30 @@ +- name: test CVE-2024-11079 loop variables preserve unsafe hostvars + hosts: localhost + gather_facts: false + tasks: + - set_fact: + foo: + safe: + prop: '{{ "{{" }} unsafe_var {{ "}}" }}' + unsafe: + prop: !unsafe '{{ unsafe_var }}' + + - name: safe var through hostvars loop is templated + assert: + that: + - item.prop == expected + loop: + - "{{ hostvars['localhost']['foo']['safe'] }}" + vars: + unsafe_var: bar + expected: bar + + - name: unsafe var through hostvars loop is not templated + assert: + that: + - item.prop == expected + loop: + - "{{ hostvars['localhost']['foo']['unsafe'] }}" + vars: + unsafe_var: bar + expected: !unsafe '{{ unsafe_var }}' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/integration/targets/template/runme.sh new/ansible_core-2.18.1/test/integration/targets/template/runme.sh --- old/ansible_core-2.18.0/test/integration/targets/template/runme.sh 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/integration/targets/template/runme.sh 2024-12-02 18:46:46.000000000 +0100 @@ -41,6 +41,10 @@ # ensure unsafe is preserved, even with extra newlines ansible-playbook unsafe.yml -v "$@" +# CVE 2024-11079 +ANSIBLE_JINJA2_NATIVE=true ansible-playbook cve-2024-11079.yml -v "$@" +ANSIBLE_JINJA2_NATIVE=false ansible-playbook cve-2024-11079.yml -v "$@" + # ensure Jinja2 overrides from a template are used ansible-playbook template_overrides.yml -v "$@" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/lib/ansible_test/_internal/containers.py new/ansible_core-2.18.1/test/lib/ansible_test/_internal/containers.py --- old/ansible_core-2.18.0/test/lib/ansible_test/_internal/containers.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/lib/ansible_test/_internal/containers.py 2024-12-02 18:46:46.000000000 +0100 @@ -292,10 +292,13 @@ current_container_id = get_docker_container_id() if current_container_id: - # Make sure any additional containers we launch use the same network as the current container we're running in. - # This is needed when ansible-test is running in a container that is not connected to Docker's default network. - container = docker_inspect(args, current_container_id, always=True) - network = container.get_network_name() + try: + # Make sure any additional containers we launch use the same network as the current container we're running in. + # This is needed when ansible-test is running in a container that is not connected to Docker's default network. + container = docker_inspect(args, current_container_id, always=True) + network = container.get_network_name() + except ContainerNotFoundError: + display.warning('Unable to detect the network for the current container. Use the `--docker-network` option if containers are unreachable.') # The default docker behavior puts containers on the same network. # The default podman behavior puts containers on isolated networks which don't allow communication between containers or network disconnect. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/lib/ansible_test/_internal/util.py new/ansible_core-2.18.1/test/lib/ansible_test/_internal/util.py --- old/ansible_core-2.18.0/test/lib/ansible_test/_internal/util.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/lib/ansible_test/_internal/util.py 2024-12-02 18:46:46.000000000 +0100 @@ -1014,15 +1014,15 @@ self._callback() -def format_command_output(stdout: str, stderr: str) -> str: +def format_command_output(stdout: str | None, stderr: str | None) -> str: """Return a formatted string containing the given stdout and stderr (if any).""" message = '' - if stderr := stderr.strip(): + if stderr and (stderr := stderr.strip()): message += '>>> Standard Error\n' message += f'{stderr}{Display.clear}\n' - if stdout := stdout.strip(): + if stdout and (stdout := stdout.strip()): message += '>>> Standard Output\n' message += f'{stdout}{Display.clear}\n' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/units/ansible_test/_internal/test_util.py new/ansible_core-2.18.1/test/units/ansible_test/_internal/test_util.py --- old/ansible_core-2.18.0/test/units/ansible_test/_internal/test_util.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.18.1/test/units/ansible_test/_internal/test_util.py 2024-12-02 18:46:46.000000000 +0100 @@ -0,0 +1,36 @@ +from __future__ import annotations + +import pytest + + +def test_failed_non_interactive_captured_command() -> None: + """Verify failed non-interactive captured commands raise a `SubprocessError` with `stdout` and `stderr` set.""" + from ansible_test._internal.util import raw_command, SubprocessError + + with pytest.raises(SubprocessError, match='Command "ls /dev/null /does/not/exist" returned exit status [0-9]+.\n>>> Standard Error\n') as error: + raw_command(['ls', '/dev/null', '/does/not/exist'], True) + + assert '/dev/null' in error.value.stdout + assert '/does/not/exist' in error.value.stderr + + +def test_failed_non_interactive_command() -> None: + """Verify failed non-interactive non-captured commands raise a `SubprocessError` with `stdout` and `stderr` set to an empty string.""" + from ansible_test._internal.util import raw_command, SubprocessError + + with pytest.raises(SubprocessError, match='Command "ls /dev/null /does/not/exist" returned exit status [0-9]+.') as error: + raw_command(['ls', '/dev/null', '/does/not/exist'], False) + + assert error.value.stdout == '' + assert error.value.stderr == '' + + +def test_failed_interactive_command() -> None: + """Verify failed interactive commands raise a `SubprocessError` with `stdout` and `stderr` set to `None`.""" + from ansible_test._internal.util import raw_command, SubprocessError + + with pytest.raises(SubprocessError, match='Command "ls /dev/null /does/not/exist" returned exit status [0-9]+.') as error: + raw_command(['ls', '/dev/null', '/does/not/exist'], False, interactive=True) + + assert error.value.stdout is None + assert error.value.stderr is None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.18.0/test/units/ansible_test/conftest.py new/ansible_core-2.18.1/test/units/ansible_test/conftest.py --- old/ansible_core-2.18.0/test/units/ansible_test/conftest.py 2024-11-04 19:35:45.000000000 +0100 +++ new/ansible_core-2.18.1/test/units/ansible_test/conftest.py 2024-12-02 18:46:46.000000000 +0100 @@ -7,7 +7,7 @@ @pytest.fixture(autouse=True, scope='session') -def ansible_test(): - """Make ansible_test available on sys.path for unit testing ansible-test.""" +def inject_ansible_test(): + """Make ansible_test available on `sys.path` for unit testing ansible-test.""" test_lib = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'lib') sys.path.insert(0, test_lib) ++++++ ansible_core-2.18.0.tar.gz.sha256 -> ansible_core-2.18.1.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.18.0.tar.gz.sha256 2024-12-02 16:58:56.756726852 +0100 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.28523/ansible_core-2.18.1.tar.gz.sha256 2024-12-05 17:10:06.321273161 +0100 @@ -1 +1 @@ -87fbebbfe8d961e9b153e84b4438ba3a327dbfdcd4ad05a6065d9ff5d9d02e7b ansible_core-2.18.0.tar.gz +14cac1f92bbdae881cb0616eddeb17925e8cb507e486087975e724533d9de74f ansible_core-2.18.1.tar.gz