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 2025-10-07 18:28:26 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/ansible-core (Old) and /work/SRC/openSUSE:Factory/.ansible-core.new.11973 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "ansible-core" Tue Oct 7 18:28:26 2025 rev:48 rq:1309466 version:2.19.3 Changes: -------- --- /work/SRC/openSUSE:Factory/ansible-core/ansible-core.changes 2025-09-22 16:41:31.406059240 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.11973/ansible-core.changes 2025-10-07 18:30:04.021764562 +0200 @@ -1,0 +2,21 @@ +Tue Oct 7 06:33:05 UTC 2025 - Johannes Kastl <[email protected]> + +- update to 2.19.3: + * Minor Changes + - fetch_file - add ca_path and cookies parameter arguments + (#85172). + * Bugfixes + - Windows async - Handle running PowerShell modules with + trailing data after the module result + - ansible-doc --list/--list_files/--metadata-dump - fixed + relative imports in nested filter/test plugin files (#85753). + - display - Fixed reference to undefined + '_DeferredWarningContext' when issuing early warnings during + startup. (#85886) + - run_command - Fixed premature selector unregistration on + empty read from stdout/stderr that caused truncated output or + hangs in rare situations. + - script inventory plugin will now show correct 'incorrect' + type when doing implicit conversions on groups. + +------------------------------------------------------------------- Old: ---- ansible_core-2.19.2.tar.gz ansible_core-2.19.2.tar.gz.sha256 New: ---- ansible_core-2.19.3.tar.gz ansible_core-2.19.3.tar.gz.sha256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ ansible-core.spec ++++++ --- /var/tmp/diff_new_pack.jxyEIz/_old 2025-10-07 18:30:06.281860155 +0200 +++ /var/tmp/diff_new_pack.jxyEIz/_new 2025-10-07 18:30:06.297860832 +0200 @@ -43,7 +43,7 @@ %endif Name: ansible-core -Version: 2.19.2 +Version: 2.19.3 Release: 0 Summary: Radically simple IT automation License: GPL-3.0-or-later ++++++ ansible_core-2.19.2.tar.gz -> ansible_core-2.19.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/PKG-INFO new/ansible_core-2.19.3/PKG-INFO --- old/ansible_core-2.19.2/PKG-INFO 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/PKG-INFO 2025-10-06 19:22:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.2 +Version: 2.19.3 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.19.2/ansible_core.egg-info/PKG-INFO new/ansible_core-2.19.3/ansible_core.egg-info/PKG-INFO --- old/ansible_core-2.19.2/ansible_core.egg-info/PKG-INFO 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/ansible_core.egg-info/PKG-INFO 2025-10-06 19:22:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: ansible-core -Version: 2.19.2 +Version: 2.19.3 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.19.2/ansible_core.egg-info/SOURCES.txt new/ansible_core-2.19.3/ansible_core.egg-info/SOURCES.txt --- old/ansible_core-2.19.2/ansible_core.egg-info/SOURCES.txt 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/ansible_core.egg-info/SOURCES.txt 2025-10-06 19:22:06.000000000 +0200 @@ -19,6 +19,7 @@ lib/ansible/release.py lib/ansible/_internal/__init__.py lib/ansible/_internal/_collection_proxy.py +lib/ansible/_internal/_display_utils.py lib/ansible/_internal/_event_formatting.py lib/ansible/_internal/_locking.py lib/ansible/_internal/_task.py @@ -875,12 +876,17 @@ test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py test/integration/targets/ansible-doc/broken-docs/collections/ansible_collections/testns/testcol/plugins/vars/noop_vars_plugin.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/MANIFEST.json +test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/__init__.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/cache/notjsonfile.py +test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/__init__.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/grouped.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/ultimatequestion.yml +test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/__init__.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/in_subdir.py +test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/nested.yml test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/inventory/statichost.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/lookup/noop.py +test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/module_utils/__init__.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/fakemodule.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/notrealmodule.py test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/modules/randommodule.py @@ -2774,6 +2780,7 @@ test/integration/targets/inventory_ini/test_types.yml test/integration/targets/inventory_script/aliases test/integration/targets/inventory_script/bad_shebang +test/integration/targets/inventory_script/bad_types test/integration/targets/inventory_script/script_inventory_fixture.py test/integration/targets/inventory_script/tasks/main.yml test/integration/targets/inventory_script/tasks/test_broken_inventory.yml @@ -4287,6 +4294,7 @@ test/integration/targets/win_app_control/templates/manifest_v1_unsafe_expression.psd1 test/integration/targets/win_async_wrapper/aliases test/integration/targets/win_async_wrapper/library/async_test.ps1 +test/integration/targets/win_async_wrapper/library/trailing_output.ps1 test/integration/targets/win_async_wrapper/tasks/main.yml test/integration/targets/win_become/aliases test/integration/targets/win_become/tasks/main.yml diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/changelogs/CHANGELOG-v2.19.rst new/ansible_core-2.19.3/changelogs/CHANGELOG-v2.19.rst --- old/ansible_core-2.19.2/changelogs/CHANGELOG-v2.19.rst 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/changelogs/CHANGELOG-v2.19.rst 2025-10-06 19:22:06.000000000 +0200 @@ -4,6 +4,29 @@ .. contents:: Topics +v2.19.3 +======= + +Release Summary +--------------- + +| Release Date: 2025-10-06 +| `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + +Minor Changes +------------- + +- fetch_file - add ca_path and cookies parameter arguments (https://github.com/ansible/ansible/issues/85172). + +Bugfixes +-------- + +- Windows async - Handle running PowerShell modules with trailing data after the module result +- ansible-doc --list/--list_files/--metadata-dump - fixed relative imports in nested filter/test plugin files (https://github.com/ansible/ansible/issues/85753). +- display - Fixed reference to undefined `_DeferredWarningContext` when issuing early warnings during startup. (https://github.com/ansible/ansible/issues/85886) +- run_command - Fixed premature selector unregistration on empty read from stdout/stderr that caused truncated output or hangs in rare situations. +- script inventory plugin will now show correct 'incorrect' type when doing implicit conversions on groups. + v2.19.2 ======= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/changelogs/changelog.yaml new/ansible_core-2.19.3/changelogs/changelog.yaml --- old/ansible_core-2.19.2/changelogs/changelog.yaml 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/changelogs/changelog.yaml 2025-10-06 19:22:06.000000000 +0200 @@ -1313,3 +1313,44 @@ - 85743-lazy-ternary.yml - ansible-test-auth-update.yml release_date: '2025-09-02' + 2.19.3: + changes: + release_summary: '| Release Date: 2025-10-06 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + + ' + codename: What Is and What Should Never Be + fragments: + - 2.19.3_summary.yaml + release_date: '2025-10-06' + 2.19.3rc1: + changes: + bugfixes: + - Windows async - Handle running PowerShell modules with trailing data after + the module result + - ansible-doc --list/--list_files/--metadata-dump - fixed relative imports in + nested filter/test plugin files (https://github.com/ansible/ansible/issues/85753). + - display - Fixed reference to undefined `_DeferredWarningContext` when issuing + early warnings during startup. (https://github.com/ansible/ansible/issues/85886) + - run_command - Fixed premature selector unregistration on empty read from stdout/stderr + that caused truncated output or hangs in rare situations. + - script inventory plugin will now show correct 'incorrect' type when doing + implicit conversions on groups. + minor_changes: + - fetch_file - add ca_path and cookies parameter arguments (https://github.com/ansible/ansible/issues/85172). + release_summary: '| Release Date: 2025-09-29 + + | `Porting Guide <https://docs.ansible.com/ansible-core/2.19/porting_guides/porting_guide_core_2.19.html>`__ + + ' + codename: What Is and What Should Never Be + fragments: + - 2.19.3rc1_summary.yaml + - display_internals.yml + - fetch_file.yml + - fix-listing-nested-filter-and-test-plugins.yml + - fix_script_error.yml + - run_command_output_selector.yml + - win_async-junk-output.yml + release_date: '2025-09-29' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/_internal/_display_utils.py new/ansible_core-2.19.3/lib/ansible/_internal/_display_utils.py --- old/ansible_core-2.19.2/lib/ansible/_internal/_display_utils.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.3/lib/ansible/_internal/_display_utils.py 2025-10-06 19:22:06.000000000 +0200 @@ -0,0 +1,145 @@ +from __future__ import annotations + +import dataclasses + +from ansible.module_utils._internal import _ambient_context, _messages +from . import _event_formatting + + +class DeferredWarningContext(_ambient_context.AmbientContextBase): + """ + Calls to `Display.warning()` and `Display.deprecated()` within this context will cause the resulting warnings to be captured and not displayed. + The intended use is for task-initiated warnings to be recorded with the task result, which makes them visible to registered results, callbacks, etc. + The active display callback is responsible for communicating any warnings to the user. + """ + + # DTFIX-FUTURE: once we start implementing nested scoped contexts for our own bookkeeping, this should be an interface facade that forwards to the nearest + # context that actually implements the warnings collection capability + + def __init__(self, *, variables: dict[str, object]) -> None: + self._variables = variables # DTFIX-FUTURE: move this to an AmbientContext-derived TaskContext (once it exists) + self._deprecation_warnings: list[_messages.DeprecationSummary] = [] + self._warnings: list[_messages.WarningSummary] = [] + self._seen: set[_messages.WarningSummary] = set() + + def capture(self, warning: _messages.WarningSummary) -> None: + """Add the warning/deprecation to the context if it has not already been seen by this context.""" + if warning in self._seen: + return + + self._seen.add(warning) + + if isinstance(warning, _messages.DeprecationSummary): + self._deprecation_warnings.append(warning) + else: + self._warnings.append(warning) + + def get_warnings(self) -> list[_messages.WarningSummary]: + """Return a list of the captured non-deprecation warnings.""" + # DTFIX-FUTURE: return a read-only list proxy instead + return self._warnings + + def get_deprecation_warnings(self) -> list[_messages.DeprecationSummary]: + """Return a list of the captured deprecation warnings.""" + # DTFIX-FUTURE: return a read-only list proxy instead + return self._deprecation_warnings + + +def format_message(summary: _messages.SummaryBase, include_traceback: bool) -> str: + if isinstance(summary, _messages.DeprecationSummary): + deprecation_message = get_deprecation_message_with_plugin_info( + msg=summary.event.msg, + version=summary.version, + date=summary.date, + deprecator=summary.deprecator, + ) + + event = dataclasses.replace(summary.event, msg=deprecation_message) + else: + event = summary.event + + return _event_formatting.format_event(event, include_traceback) + + +def get_deprecation_message_with_plugin_info( + *, + msg: str, + version: str | None, + removed: bool = False, + date: str | None, + deprecator: _messages.PluginInfo | None, +) -> str: + """Internal use only. Return a deprecation message and help text for display.""" + # DTFIX-FUTURE: the logic for omitting date/version doesn't apply to the payload, so it shows up in vars in some cases when it should not + + if removed: + removal_fragment = 'This feature was removed' + else: + removal_fragment = 'This feature will be removed' + + if not deprecator or not deprecator.type: + # indeterminate has no resolved_name or type + # collections have a resolved_name but no type + collection = deprecator.resolved_name if deprecator else None + plugin_fragment = '' + elif deprecator.resolved_name == 'ansible.builtin': + # core deprecations from base classes (the API) have no plugin name, only 'ansible.builtin' + plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin' + + collection = deprecator.resolved_name + plugin_fragment = f'the {plugin_type_name} API' + else: + parts = deprecator.resolved_name.split('.') + plugin_name = parts[-1] + plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin' + + collection = '.'.join(parts[:2]) if len(parts) > 2 else None + plugin_fragment = f'{plugin_type_name} {plugin_name!r}' + + if collection and plugin_fragment: + plugin_fragment += ' in' + + if collection == 'ansible.builtin': + collection_fragment = 'ansible-core' + elif collection: + collection_fragment = f'collection {collection!r}' + else: + collection_fragment = '' + + if not collection: + when_fragment = 'in the future' if not removed else '' + elif date: + when_fragment = f'in a release after {date}' + elif version: + when_fragment = f'version {version}' + else: + when_fragment = 'in a future release' if not removed else '' + + if plugin_fragment or collection_fragment: + from_fragment = 'from' + else: + from_fragment = '' + + deprecation_msg = ' '.join(f for f in [removal_fragment, from_fragment, plugin_fragment, collection_fragment, when_fragment] if f) + '.' + + return join_sentences(msg, deprecation_msg) + + +def join_sentences(first: str | None, second: str | None) -> str: + """Join two sentences together.""" + first = (first or '').strip() + second = (second or '').strip() + + if first and first[-1] not in ('!', '?', '.'): + first += '.' + + if second and second[-1] not in ('!', '?', '.'): + second += '.' + + if first and not second: + return first + + if not first and second: + return second + + return ' '.join((first, second)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/executor/powershell/async_watchdog.ps1 new/ansible_core-2.19.3/lib/ansible/executor/powershell/async_watchdog.ps1 --- old/ansible_core-2.19.2/lib/ansible/executor/powershell/async_watchdog.ps1 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/executor/powershell/async_watchdog.ps1 2025-10-06 19:22:06.000000000 +0200 @@ -67,14 +67,34 @@ $result.finished = $true if ($jobAsyncResult.IsCompleted) { - $jobOutput = $ps.EndInvoke($jobAsyncResult) + $jobOutput = @($ps.EndInvoke($jobAsyncResult) | Out-String) -join "`n" $jobError = $ps.Streams.Error # write success/output/error to result object - # TODO: cleanse leading/trailing junk - $moduleResult = $jobOutput | ConvertFrom-Json | Convert-JsonObject + $moduleResultJson = $jobOutput + $startJsonChar = $moduleResultJson.IndexOf([char]'{') + if ($startJsonChar -eq -1) { + throw "No start of json char found in module result" + } + $moduleResultJson = $moduleResultJson.Substring($startJsonChar) + + $endJsonChar = $moduleResultJson.LastIndexOf([char]'}') + if ($endJsonChar -eq -1) { + throw "No end of json char found in module result" + } + + $trailingJunk = $moduleResultJson.Substring($endJsonChar + 1).Trim() + $moduleResultJson = $moduleResultJson.Substring(0, $endJsonChar + 1) + $moduleResult = $moduleResultJson | ConvertFrom-Json | Convert-JsonObject # TODO: check for conflicting keys $result = $result + $moduleResult + + if ($trailingJunk) { + if (-not $result.warnings) { + $result.warnings = @() + } + $result.warnings += "Module invocation had junk after the JSON data: $trailingJunk" + } } else { # We can't call Stop() as pwsh won't respond if it is busy calling a .NET @@ -103,7 +123,7 @@ $result.failed = $true $result.msg = "failure during async watchdog: $_" # return output back, if available, to Ansible to help with debugging errors - $result.stdout = $jobOutput | Out-String + $result.stdout = $jobOutput $result.stderr = $jobError | Out-String } finally { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/executor/task_executor.py new/ansible_core-2.19.3/lib/ansible/executor/task_executor.py --- old/ansible_core-2.19.2/lib/ansible/executor/task_executor.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/executor/task_executor.py 2025-10-06 19:22:06.000000000 +0200 @@ -19,6 +19,8 @@ AnsibleError, AnsibleParserError, AnsibleUndefinedVariable, AnsibleTaskError, AnsibleValueOmittedError, ) + +from ansible._internal import _display_utils from ansible.executor.task_result import _RawTaskResult from ansible._internal._datatag import _utils from ansible.module_utils._internal import _messages @@ -35,7 +37,7 @@ from ansible._internal._templating._engine import TemplateEngine from ansible.template import Templar from ansible.utils.collection_loader import AnsibleCollectionConfig -from ansible.utils.display import Display, _DeferredWarningContext +from ansible.utils.display import Display from ansible.utils.vars import combine_vars from ansible.vars.clean import namespace_facts, clean_facts from ansible.vars.manager import _deprecate_top_level_fact @@ -416,7 +418,7 @@ def _execute(self, templar: TemplateEngine, variables: dict[str, t.Any]) -> dict[str, t.Any]: result: dict[str, t.Any] - with _DeferredWarningContext(variables=variables) as warning_ctx: + with _display_utils.DeferredWarningContext(variables=variables) as warning_ctx: try: # DTFIX-FUTURE: improve error handling to prioritize the earliest exception, turning the remaining ones into warnings result = self._execute_internal(templar, variables) @@ -431,7 +433,7 @@ self._task.update_result_no_log(templar, result) - # The warnings/deprecations in the result have already been captured in the _DeferredWarningContext by _apply_task_result_compat. + # The warnings/deprecations in the result have already been captured in the DeferredWarningContext by _apply_task_result_compat. # The captured warnings/deprecations are a superset of the ones from the result, and may have been converted from a dict to a dataclass. # These are then used to supersede the entries in the result. @@ -788,7 +790,7 @@ return result @staticmethod - def _apply_task_result_compat(result: dict[str, t.Any], warning_ctx: _DeferredWarningContext) -> None: + def _apply_task_result_compat(result: dict[str, t.Any], warning_ctx: _display_utils.DeferredWarningContext) -> None: """Apply backward-compatibility mutations to the supplied task result.""" if warnings := result.get('warnings'): if isinstance(warnings, list): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/module_utils/ansible_release.py new/ansible_core-2.19.3/lib/ansible/module_utils/ansible_release.py --- old/ansible_core-2.19.2/lib/ansible/module_utils/ansible_release.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/module_utils/ansible_release.py 2025-10-06 19:22:06.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.2' +__version__ = '2.19.3' __author__ = 'Ansible, Inc.' __codename__ = "What Is and What Should Never Be" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/module_utils/basic.py new/ansible_core-2.19.3/lib/ansible/module_utils/basic.py --- old/ansible_core-2.19.2/lib/ansible/module_utils/basic.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/module_utils/basic.py 2025-10-06 19:22:06.000000000 +0200 @@ -2090,7 +2090,7 @@ stdout_changed = False for key, event in events: b_chunk = key.fileobj.read(32768) - if not b_chunk: + if not b_chunk and b_chunk is not None: selector.unregister(key.fileobj) elif key.fileobj == cmd.stdout: stdout += b_chunk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/module_utils/urls.py new/ansible_core-2.19.3/lib/ansible/module_utils/urls.py --- old/ansible_core-2.19.2/lib/ansible/module_utils/urls.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/module_utils/urls.py 2025-10-06 19:22:06.000000000 +0200 @@ -1358,7 +1358,8 @@ def fetch_file(module, url, data=None, headers=None, method=None, use_proxy=True, force=False, last_mod_time=None, timeout=10, - unredirected_headers=None, decompress=True, ciphers=None): + unredirected_headers=None, decompress=True, ciphers=None, + ca_path=None, cookies=None): """Download and save a file via HTTP(S) or FTP (needs the module as parameter). This is basically a wrapper around fetch_url(). @@ -1375,6 +1376,8 @@ :kwarg unredirected_headers: (optional) A list of headers to not attach on a redirected request :kwarg decompress: (optional) Whether to attempt to decompress gzip content-encoded responses :kwarg ciphers: (optional) List of ciphers to use + :kwarg ca_path: (optional) Path to CA bundle + :kwarg cookies: (optional) CookieJar object to send with the request :returns: A string, the path to the downloaded file. """ @@ -1386,7 +1389,8 @@ module.add_cleanup_file(fetch_temp_file.name) try: rsp, info = fetch_url(module, url, data, headers, method, use_proxy, force, last_mod_time, timeout, - unredirected_headers=unredirected_headers, decompress=decompress, ciphers=ciphers) + unredirected_headers=unredirected_headers, decompress=decompress, ciphers=ciphers, + ca_path=ca_path, cookies=cookies) if not rsp or (rsp.code and rsp.code >= 400): module.fail_json(msg="Failure downloading %s, %s" % (url, info['msg'])) data = rsp.read(bufsize) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/plugins/inventory/script.py new/ansible_core-2.19.3/lib/ansible/plugins/inventory/script.py --- old/ansible_core-2.19.2/lib/ansible/plugins/inventory/script.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/plugins/inventory/script.py 2025-10-06 19:22:06.000000000 +0200 @@ -256,9 +256,10 @@ group = self.inventory.add_group(group) if not isinstance(data, dict): + original_type = native_type_name(data) data = {'hosts': data} display.deprecated( - msg=f"Group {group!r} was converted to {native_type_name(dict)!r} from {native_type_name(data)!r}.", + msg=f"Group {group!r} was converted to {native_type_name(dict)!r} from {original_type!r}.", version='2.23', obj=origin, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/plugins/list.py new/ansible_core-2.19.3/lib/ansible/plugins/list.py --- old/ansible_core-2.19.2/lib/ansible/plugins/list.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/plugins/list.py 2025-10-06 19:22:06.000000000 +0200 @@ -105,18 +105,25 @@ ]): continue + resource_dir = to_native(os.path.dirname(full_path)) + resource_name = get_composite_name(collection, plugin, resource_dir, depth) + if ptype in ('test', 'filter'): + # NOTE: pass the composite resource to ensure any relative + # imports it contains are interpreted in the correct context + if collection: + resource_name = '.'.join(resource_name.split('.')[2:]) try: - file_plugins = _list_j2_plugins_from_file(collection, full_path, ptype, plugin) + file_plugins = _list_j2_plugins_from_file(collection, full_path, ptype, resource_name) except KeyError as e: display.warning('Skipping file %s: %s' % (full_path, to_native(e))) continue for plugin in file_plugins: - plugin_name = get_composite_name(collection, plugin.ansible_name, os.path.dirname(to_native(full_path)), depth) + plugin_name = get_composite_name(collection, plugin.ansible_name, resource_dir, depth) plugins[plugin_name] = full_path else: - plugin_name = get_composite_name(collection, plugin, os.path.dirname(to_native(full_path)), depth) + plugin_name = resource_name plugins[plugin_name] = full_path else: display.debug("Skip listing plugins in '{0}' as it is not a directory".format(path)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/plugins/loader.py new/ansible_core-2.19.3/lib/ansible/plugins/loader.py --- old/ansible_core-2.19.2/lib/ansible/plugins/loader.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/plugins/loader.py 2025-10-06 19:22:06.000000000 +0200 @@ -36,6 +36,7 @@ from ansible.utils.display import Display from ansible.utils.plugin_docs import add_fragments from ansible._internal._datatag import _tags +from ansible._internal import _display_utils from . import _AnsiblePluginInfoMixin from .filter import AnsibleJinja2Filter @@ -606,7 +607,7 @@ warning_text = tombstone.get('warning_text') or '' warning_plugin_type = "module" if self.type == "modules" else f'{self.type} plugin' warning_text = f'The {fq_name!r} {warning_plugin_type} has been removed.{" " if warning_text else ""}{warning_text}' - removed_msg = display._get_deprecation_message_with_plugin_info( + removed_msg = _display_utils.get_deprecation_message_with_plugin_info( msg=warning_text, version=removal_version, date=removal_date, @@ -1411,7 +1412,7 @@ removal_version = tombstone_entry.get('removal_version') warning_text = f'The {key!r} {self.type} plugin has been removed.{" " if warning_text else ""}{warning_text}' - exc_msg = display._get_deprecation_message_with_plugin_info( + exc_msg = _display_utils.get_deprecation_message_with_plugin_info( msg=warning_text, version=removal_version, date=removal_date, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/release.py new/ansible_core-2.19.3/lib/ansible/release.py --- old/ansible_core-2.19.2/lib/ansible/release.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/release.py 2025-10-06 19:22:06.000000000 +0200 @@ -17,6 +17,6 @@ from __future__ import annotations -__version__ = '2.19.2' +__version__ = '2.19.3' __author__ = 'Ansible, Inc.' __codename__ = "What Is and What Should Never Be" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/lib/ansible/utils/display.py new/ansible_core-2.19.3/lib/ansible/utils/display.py --- old/ansible_core-2.19.2/lib/ansible/utils/display.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/lib/ansible/utils/display.py 2025-10-06 19:22:06.000000000 +0200 @@ -18,7 +18,6 @@ from __future__ import annotations import contextlib -import dataclasses try: import curses @@ -52,8 +51,8 @@ from ansible.constants import config from ansible.errors import AnsibleAssertionError, AnsiblePromptInterrupt, AnsiblePromptNoninteractive, AnsibleError from ansible._internal._errors import _error_utils, _error_factory -from ansible._internal import _event_formatting -from ansible.module_utils._internal import _ambient_context, _deprecator, _messages +from ansible._internal import _display_utils +from ansible.module_utils._internal import _deprecator, _messages from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.datatag import deprecator_from_collection_name from ansible._internal._datatag._tags import TrustedAsTemplate @@ -100,6 +99,17 @@ _traceback._is_traceback_enabled = _is_controller_traceback_enabled +def _deprecation_warnings_enabled() -> bool: + """Return True if deprecation warnings are enabled for the current calling context, otherwise False.""" + # DTFIX-FUTURE: move this capability into config using an AmbientContext-derived TaskContext (once it exists) + if warning_ctx := _display_utils.DeferredWarningContext.current(optional=True): + variables = warning_ctx._variables + else: + variables = None + + return C.config.get_config_value('DEPRECATION_WARNINGS', variables=variables) + + def get_text_width(text: str) -> int: """Function that utilizes ``wcswidth`` or ``wcwidth`` to determine the number of columns used to display a text string. @@ -582,7 +592,7 @@ version="2.23", ) - msg = self._get_deprecation_message_with_plugin_info( + msg = _display_utils.get_deprecation_message_with_plugin_info( msg=msg, version=version, removed=removed, @@ -597,70 +607,6 @@ return msg - def _get_deprecation_message_with_plugin_info( - self, - *, - msg: str, - version: str | None, - removed: bool = False, - date: str | None, - deprecator: _messages.PluginInfo | None, - ) -> str: - """Internal use only. Return a deprecation message and help text for display.""" - # DTFIX-FUTURE: the logic for omitting date/version doesn't apply to the payload, so it shows up in vars in some cases when it should not - - if removed: - removal_fragment = 'This feature was removed' - else: - removal_fragment = 'This feature will be removed' - - if not deprecator or not deprecator.type: - # indeterminate has no resolved_name or type - # collections have a resolved_name but no type - collection = deprecator.resolved_name if deprecator else None - plugin_fragment = '' - elif deprecator.resolved_name == 'ansible.builtin': - # core deprecations from base classes (the API) have no plugin name, only 'ansible.builtin' - plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin' - - collection = deprecator.resolved_name - plugin_fragment = f'the {plugin_type_name} API' - else: - parts = deprecator.resolved_name.split('.') - plugin_name = parts[-1] - plugin_type_name = str(deprecator.type) if deprecator.type is _messages.PluginType.MODULE else f'{deprecator.type} plugin' - - collection = '.'.join(parts[:2]) if len(parts) > 2 else None - plugin_fragment = f'{plugin_type_name} {plugin_name!r}' - - if collection and plugin_fragment: - plugin_fragment += ' in' - - if collection == 'ansible.builtin': - collection_fragment = 'ansible-core' - elif collection: - collection_fragment = f'collection {collection!r}' - else: - collection_fragment = '' - - if not collection: - when_fragment = 'in the future' if not removed else '' - elif date: - when_fragment = f'in a release after {date}' - elif version: - when_fragment = f'version {version}' - else: - when_fragment = 'in a future release' if not removed else '' - - if plugin_fragment or collection_fragment: - from_fragment = 'from' - else: - from_fragment = '' - - deprecation_msg = ' '.join(f for f in [removal_fragment, from_fragment, plugin_fragment, collection_fragment, when_fragment] if f) + '.' - - return _join_sentences(msg, deprecation_msg) - @staticmethod def _deduplicate(msg: str, messages: set[str]) -> bool: """ @@ -729,7 +675,7 @@ _skip_stackwalk = True if removed: - formatted_msg = self._get_deprecation_message_with_plugin_info( + formatted_msg = _display_utils.get_deprecation_message_with_plugin_info( msg=msg, version=version, removed=removed, @@ -756,7 +702,7 @@ deprecator=deprecator, ) - if warning_ctx := _DeferredWarningContext.current(optional=True): + if warning_ctx := _display_utils.DeferredWarningContext.current(optional=True): warning_ctx.capture(deprecation) return @@ -769,12 +715,12 @@ # This is the post-proxy half of the `deprecated` implementation. # Any logic that must occur in the primary controller process needs to be implemented here. - if not _DeferredWarningContext.deprecation_warnings_enabled(): + if not _deprecation_warnings_enabled(): return self.warning('Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg.') - msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED)) + msg = _display_utils.format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.DEPRECATED)) msg = f'[DEPRECATION WARNING]: {msg}' if self._deduplicate(msg, self._deprecations): @@ -812,7 +758,7 @@ ), ) - if warning_ctx := _DeferredWarningContext.current(optional=True): + if warning_ctx := _display_utils.DeferredWarningContext.current(optional=True): warning_ctx.capture(warning) return @@ -825,7 +771,7 @@ # This is the post-proxy half of the `warning` implementation. # Any logic that must occur in the primary controller process needs to be implemented here. - msg = _format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING)) + msg = _display_utils.format_message(warning, _traceback.is_traceback_enabled(_traceback.TracebackEvent.WARNING)) msg = f"[WARNING]: {msg}" if self._deduplicate(msg, self._warns): @@ -915,7 +861,7 @@ event=event, ) - if warning_ctx := _DeferredWarningContext.current(optional=True): + if warning_ctx := _display_utils.DeferredWarningContext.current(optional=True): warning_ctx.capture(warning) return @@ -952,7 +898,7 @@ # This is the post-proxy half of the `error` implementation. # Any logic that must occur in the primary controller process needs to be implemented here. - msg = _format_message(error, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)) + msg = _display_utils.format_message(error, _traceback.is_traceback_enabled(_traceback.TracebackEvent.ERROR)) msg = f'[ERROR]: {msg}' if self._deduplicate(msg, self._errors): @@ -1173,92 +1119,6 @@ _display = Display() -class _DeferredWarningContext(_ambient_context.AmbientContextBase): - """ - Calls to `Display.warning()` and `Display.deprecated()` within this context will cause the resulting warnings to be captured and not displayed. - The intended use is for task-initiated warnings to be recorded with the task result, which makes them visible to registered results, callbacks, etc. - The active display callback is responsible for communicating any warnings to the user. - """ - - # DTFIX-FUTURE: once we start implementing nested scoped contexts for our own bookkeeping, this should be an interface facade that forwards to the nearest - # context that actually implements the warnings collection capability - - def __init__(self, *, variables: dict[str, object]) -> None: - self._variables = variables # DTFIX-FUTURE: move this to an AmbientContext-derived TaskContext (once it exists) - self._deprecation_warnings: list[_messages.DeprecationSummary] = [] - self._warnings: list[_messages.WarningSummary] = [] - self._seen: set[_messages.WarningSummary] = set() - - @classmethod - def deprecation_warnings_enabled(cls) -> bool: - """Return True if deprecation warnings are enabled for the current calling context, otherwise False.""" - # DTFIX-FUTURE: move this capability into config using an AmbientContext-derived TaskContext (once it exists) - if warning_ctx := cls.current(optional=True): - variables = warning_ctx._variables - else: - variables = None - - return C.config.get_config_value('DEPRECATION_WARNINGS', variables=variables) - - def capture(self, warning: _messages.WarningSummary) -> None: - """Add the warning/deprecation to the context if it has not already been seen by this context.""" - if warning in self._seen: - return - - self._seen.add(warning) - - if isinstance(warning, _messages.DeprecationSummary): - self._deprecation_warnings.append(warning) - else: - self._warnings.append(warning) - - def get_warnings(self) -> list[_messages.WarningSummary]: - """Return a list of the captured non-deprecation warnings.""" - # DTFIX-FUTURE: return a read-only list proxy instead - return self._warnings - - def get_deprecation_warnings(self) -> list[_messages.DeprecationSummary]: - """Return a list of the captured deprecation warnings.""" - # DTFIX-FUTURE: return a read-only list proxy instead - return self._deprecation_warnings - - -def _join_sentences(first: str | None, second: str | None) -> str: - """Join two sentences together.""" - first = (first or '').strip() - second = (second or '').strip() - - if first and first[-1] not in ('!', '?', '.'): - first += '.' - - if second and second[-1] not in ('!', '?', '.'): - second += '.' - - if first and not second: - return first - - if not first and second: - return second - - return ' '.join((first, second)) - - -def _format_message(summary: _messages.SummaryBase, include_traceback: bool) -> str: - if isinstance(summary, _messages.DeprecationSummary): - deprecation_message = _display._get_deprecation_message_with_plugin_info( - msg=summary.event.msg, - version=summary.version, - date=summary.date, - deprecator=summary.deprecator, - ) - - event = dataclasses.replace(summary.event, msg=deprecation_message) - else: - event = summary.event - - return _event_formatting.format_event(event, include_traceback) - - def _report_config_warnings(deprecator: _messages.PluginInfo) -> None: """Called by config to report warnings/deprecations collected during a config parse.""" while config._errors: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/in_subdir.py new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/in_subdir.py --- old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/in_subdir.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/in_subdir.py 2025-10-06 19:22:06.000000000 +0200 @@ -2,7 +2,9 @@ from __future__ import annotations -from ansible.utils.display import Display +from ansible_collections.testns.testcol.plugins.module_utils import Display +# Test for https://github.com/ansible/ansible/issues/85754 +from ...module_utils import Display display = Display() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/nested.yml new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/nested.yml --- old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/nested.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/filter/filter_subdir/nested.yml 2025-10-06 19:22:06.000000000 +0200 @@ -0,0 +1,7 @@ +DOCUMENTATION: + description: filter plugin in a subdirectory + author: ansible-core + options: + _input: + description: input data, which does nothing + type: raw diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/module_utils/__init__.py new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/module_utils/__init__.py --- old/ansible_core-2.19.2/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/module_utils/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.3/test/integration/targets/ansible-doc/collections/ansible_collections/testns/testcol/plugins/module_utils/__init__.py 2025-10-06 19:22:06.000000000 +0200 @@ -0,0 +1,3 @@ +from __future__ import annotations + +from ansible.utils.display import Display diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/ansible-doc/runme.sh new/ansible_core-2.19.3/test/integration/targets/ansible-doc/runme.sh --- old/ansible_core-2.19.2/test/integration/targets/ansible-doc/runme.sh 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/ansible-doc/runme.sh 2025-10-06 19:22:06.000000000 +0200 @@ -243,6 +243,7 @@ [ "$(ansible-doc -t test --playbook-dir ./ testns.testcol.yolo| wc -l)" -gt "0" ] [ "$(ansible-doc -t filter --playbook-dir ./ donothing| wc -l)" -gt "0" ] [ "$(ansible-doc -t filter --playbook-dir ./ ansible.legacy.donothing| wc -l)" -gt "0" ] +[ "$(ansible-doc -t filter --playbook-dir ./ testns.testcol.filter_subdir.nested| wc -l)" -gt "0" ] echo "testing no docs and no sidecar" ansible-doc -t filter --playbook-dir ./ nodocs 2>&1| grep "${GREP_OPTS[@]}" -c 'missing documentation' || true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/inventory_script/bad_types new/ansible_core-2.19.3/test/integration/targets/inventory_script/bad_types --- old/ansible_core-2.19.2/test/integration/targets/inventory_script/bad_types 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.3/test/integration/targets/inventory_script/bad_types 2025-10-06 19:22:06.000000000 +0200 @@ -0,0 +1,13 @@ +#!/bin/sh + +echo '{ + "good_group": { + "vars": { + "test1": "value1", + "test2": "value2" + }, + "hosts": ["example1", "example2"] + }, + "bad_group": "should be list", + "_meta": {} +}' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/main.yml new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/main.yml --- old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/main.yml 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/main.yml 2025-10-06 19:22:06.000000000 +0200 @@ -1,95 +1,103 @@ -- name: run valid script output test cases - include_tasks: test_valid_inventory.yml - loop: - - mode: no_profile - show_stderr: '1' - emit_stderr: '1' - assertions: &standard_assertions - - inventory_data | length == 5 - - - inventory_data._meta | length == 2 - - inventory_data._meta.hostvars.host1.a_host1_hostvar == "avalue" - - inventory_data._meta.hostvars.host1.a_host1_hostvar is ansible._protomatter.tagged_with "TrustedAsTemplate" - - inventory_data._meta.hostvars.localhost.a_localhost_hostvar == "avalue" - - inventory_data._meta.hostvars.localhost.a_localhost_hostvar is ansible._protomatter.tagged_with "TrustedAsTemplate" - - - inventory_data.all | length == 1 - - inventory_data.all.children | symmetric_difference(["ungrouped", "group1", "empty_group", "list_as_group", "rewrite_as_host"]) | length == 0 - - - inventory_data.group1 | length == 2 - - inventory_data.group1.hosts == ["host1"] - - - inventory_data.group1.vars | length == 2 - - inventory_data.group1.vars.a_group1_groupvar == "avalue" - - inventory_data.group1.vars.a_group1_groupvar is ansible._protomatter.tagged_with "TrustedAsTemplate" - - inventory_data.group1.vars.group1_untrusted_var == "untrusted value" - - inventory_data.group1.vars.group1_untrusted_var is not ansible._protomatter.tagged_with "TrustedAsTemplate" - - - inventory_data.rewrite_as_host | length == 2 - - inventory_data.rewrite_as_host.hosts == ["rewrite_as_host"] - - inventory_data.rewrite_as_host.vars.avar == "value" - - inventory_data.rewrite_as_host.vars.avar is not ansible._protomatter.tagged_with "TrustedAsTemplate" # rewritten groups are too hard to trust and are deprecated - - inv_out.stderr is contains "Treating malformed group 'rewrite_as_host'" - - inventory_data.rewrite_as_host.vars.untrusted_var == "untrusted value" - - inventory_data.rewrite_as_host.vars.untrusted_var is not ansible._protomatter.tagged_with "TrustedAsTemplate" - - - inventory_data.ungrouped | length == 1 - - inventory_data.ungrouped.hosts == ["localhost"] - - - mode: with_profile - show_stderr: '1' - assertions: *standard_assertions - - - mode: no_hosts - assertions: - - inventory_data | length == 2 - - inventory_data._meta.hostvars == {} - - - inventory_data.all | length == 1 - - inventory_data.all.children == ["ungrouped"] - - - mode: no_meta_hostvars - assertions: - - inventory_data | length == 3 - - inventory_data._meta.hostvars | length == 1 - - inventory_data._meta.hostvars.myhost.avar == "avalue" - - inventory_data._meta.hostvars.myhost.avar is ansible._protomatter.tagged_with "TrustedAsTemplate" - - inventory_data._meta.hostvars.myhost.untrusted == "untrusted value" - - inventory_data._meta.hostvars.myhost.untrusted is not ansible._protomatter.tagged_with "TrustedAsTemplate" - - - inventory_data.all | length == 1 - - inventory_data.all.children | symmetric_difference(["ungrouped", "mygroup"]) | length == 0 - - - inventory_data.mygroup | length == 1 - - inventory_data.mygroup.hosts == ["myhost"] - - - mode: no_meta_hostvars_empty_host_result - assertions: - - inventory_data | length == 3 - - inventory_data._meta.hostvars == {} - - - inventory_data.all | length == 1 - - inventory_data.all.children | symmetric_difference(["ungrouped", "mygroup"]) | length == 0 - - - inventory_data.mygroup | length == 1 - - inventory_data.mygroup.hosts == ["myhost"] - -- name: run invalid script output test cases - include_tasks: test_broken_inventory.yml - loop: - - {mode: bad_shebang, script_name: bad_shebang, expected_error: Failed to execute inventory script command} - - {mode: non_zero_exit, expected_error: Inventory script returned non-zero exit code 1} - - {mode: invalid_utf8, expected_error: Inventory script result contained characters that cannot be interpreted as UTF-8} - - {mode: invalid_json, expected_error: Unable to get JSON decoder for inventory script result. Value could not be parsed as JSON} - - {mode: invalid_type, expected_error: Unable to get JSON decoder for inventory script result. Value is 'str' instead of 'dict'} - - {mode: invalid_meta_type, expected_error: Unable to get JSON decoder for inventory script result. Value contains '_meta' which is 'str' instead of 'dict'} - - {mode: invalid_profile_type, expected_error: Unable to get JSON decoder for inventory script result. Value contains '_meta.profile' which is 'int' instead of 'str'} - - {mode: invalid_profile_name, expected_error: Non-inventory profile 'invalid_profile' is not allowed.} - - {mode: invalid_inventory_profile_name, expected_error: Unable to get JSON decoder for inventory script result. Unknown profile name 'inventory_invalid_profile'} - - {mode: invalid_json_for_profile, expected_error: Inventory script result could not be parsed as JSON} - - {mode: invalid_meta_hostvars_type, expected_error: Value contains '_meta.hostvars' which is 'list' instead of 'dict'} - - {mode: invalid_meta_hostvars_type_for_host, expected_error: Invalid data from file, expected dictionary and got} - - {mode: invalid_group_type, expected_error: Value contains 'mygroup.hosts' which is 'NoneType' instead of 'list'} - - {mode: invalid_group_vars_type, expected_error: Value contains 'mygroup.vars' which is 'list' instead of 'dict'} - - {mode: no_meta_hostvars_host_nonzero_rc, expected_error: Inventory script returned non-zero exit code 1} - - {mode: no_meta_hostvars_host_invalid_json, expected_error: Inventory script result for host 'myhost' could not be parsed as JSON} +- name: Restrict tests to 'script' + environment: + INVENTORY_TEST_MODE: '{{ item.mode | default(omit) }}' + block: + - name: run valid script output test cases + include_tasks: test_valid_inventory.yml + loop: + - mode: no_profile + show_stderr: '1' + emit_stderr: '1' + assertions: &standard_assertions + - inventory_data | length == 5 + + - inventory_data._meta | length == 2 + - inventory_data._meta.hostvars.host1.a_host1_hostvar == "avalue" + - inventory_data._meta.hostvars.host1.a_host1_hostvar is ansible._protomatter.tagged_with "TrustedAsTemplate" + - inventory_data._meta.hostvars.localhost.a_localhost_hostvar == "avalue" + - inventory_data._meta.hostvars.localhost.a_localhost_hostvar is ansible._protomatter.tagged_with "TrustedAsTemplate" + + - inventory_data.all | length == 1 + - inventory_data.all.children | symmetric_difference(["ungrouped", "group1", "empty_group", "list_as_group", "rewrite_as_host"]) | length == 0 + + - inventory_data.group1 | length == 2 + - inventory_data.group1.hosts == ["host1"] + + - inventory_data.group1.vars | length == 2 + - inventory_data.group1.vars.a_group1_groupvar == "avalue" + - inventory_data.group1.vars.a_group1_groupvar is ansible._protomatter.tagged_with "TrustedAsTemplate" + - inventory_data.group1.vars.group1_untrusted_var == "untrusted value" + - inventory_data.group1.vars.group1_untrusted_var is not ansible._protomatter.tagged_with "TrustedAsTemplate" + + - inventory_data.rewrite_as_host | length == 2 + - inventory_data.rewrite_as_host.hosts == ["rewrite_as_host"] + - inventory_data.rewrite_as_host.vars.avar == "value" + - inventory_data.rewrite_as_host.vars.avar is not ansible._protomatter.tagged_with "TrustedAsTemplate" # rewritten groups are too hard to trust and are deprecated + - inv_out.stderr is contains "Treating malformed group 'rewrite_as_host'" + - inventory_data.rewrite_as_host.vars.untrusted_var == "untrusted value" + - inventory_data.rewrite_as_host.vars.untrusted_var is not ansible._protomatter.tagged_with "TrustedAsTemplate" + + - inventory_data.ungrouped | length == 1 + - inventory_data.ungrouped.hosts == ["localhost"] + + - mode: with_profile + show_stderr: '1' + assertions: *standard_assertions + + - mode: no_hosts + assertions: + - inventory_data | length == 2 + - inventory_data._meta.hostvars == {} + + - inventory_data.all | length == 1 + - inventory_data.all.children == ["ungrouped"] + + - mode: no_meta_hostvars + assertions: + - inventory_data | length == 3 + - inventory_data._meta.hostvars | length == 1 + - inventory_data._meta.hostvars.myhost.avar == "avalue" + - inventory_data._meta.hostvars.myhost.avar is ansible._protomatter.tagged_with "TrustedAsTemplate" + - inventory_data._meta.hostvars.myhost.untrusted == "untrusted value" + - inventory_data._meta.hostvars.myhost.untrusted is not ansible._protomatter.tagged_with "TrustedAsTemplate" + + - inventory_data.all | length == 1 + - inventory_data.all.children | symmetric_difference(["ungrouped", "mygroup"]) | length == 0 + + - inventory_data.mygroup | length == 1 + - inventory_data.mygroup.hosts == ["myhost"] + + - mode: no_meta_hostvars_empty_host_result + assertions: + - inventory_data | length == 3 + - inventory_data._meta.hostvars == {} + + - inventory_data.all | length == 1 + - inventory_data.all.children | symmetric_difference(["ungrouped", "mygroup"]) | length == 0 + + - inventory_data.mygroup | length == 1 + - inventory_data.mygroup.hosts == ["myhost"] + + - name: run invalid script output test cases + include_tasks: test_broken_inventory.yml + loop: + - {mode: bad_shebang, script_name: bad_shebang, expected_error: Failed to execute inventory script command} + - {mode: non_zero_exit, expected_error: Inventory script returned non-zero exit code 1} + - {mode: invalid_utf8, expected_error: Inventory script result contained characters that cannot be interpreted as UTF-8} + - {mode: invalid_json, expected_error: Unable to get JSON decoder for inventory script result. Value could not be parsed as JSON} + - {mode: invalid_type, expected_error: Unable to get JSON decoder for inventory script result. Value is 'str' instead of 'dict'} + - {mode: invalid_meta_type, expected_error: Unable to get JSON decoder for inventory script result. Value contains '_meta' which is 'str' instead of 'dict'} + - {mode: invalid_profile_type, expected_error: Unable to get JSON decoder for inventory script result. Value contains '_meta.profile' which is 'int' instead of 'str'} + - {mode: invalid_profile_name, expected_error: Non-inventory profile 'invalid_profile' is not allowed.} + - {mode: invalid_inventory_profile_name, expected_error: Unable to get JSON decoder for inventory script result. Unknown profile name 'inventory_invalid_profile'} + - {mode: invalid_json_for_profile, expected_error: Inventory script result could not be parsed as JSON} + - {mode: invalid_meta_hostvars_type, expected_error: Value contains '_meta.hostvars' which is 'list' instead of 'dict'} + - {mode: invalid_meta_hostvars_type_for_host, expected_error: Invalid data from file, expected dictionary and got} + - {mode: invalid_group_type, expected_error: Value contains 'mygroup.hosts' which is 'NoneType' instead of 'list'} + - {mode: invalid_group_vars_type, expected_error: Value contains 'mygroup.vars' which is 'list' instead of 'dict'} + - {mode: no_meta_hostvars_host_nonzero_rc, expected_error: Inventory script returned non-zero exit code 1} + - {mode: no_meta_hostvars_host_invalid_json, expected_error: Inventory script result for host 'myhost' could not be parsed as JSON} + - mode: bad_types + script_name: bad_types + deprecation: "Group 'bad_group' was converted to 'dict' from 'str'" # this deprecation is removed in 2.23 + expected_error: "Value contains 'bad_group.hosts' which is 'str' instead of 'list'" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/test_broken_inventory.yml new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/test_broken_inventory.yml --- old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/test_broken_inventory.yml 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/test_broken_inventory.yml 2025-10-06 19:22:06.000000000 +0200 @@ -2,8 +2,8 @@ shell: ansible-inventory -i {{ role_path | quote }}/{{ item.script_name | default('script_inventory_fixture.py') }} --list --export changed_when: false environment: - INVENTORY_TEST_MODE: '{{ item.mode | default(omit) }}' INVENTORY_EMIT_STDERR: '1' + ANSIBLE_DEPRECATION_WARNINGS: '{{ "deprecation" in item }}' ignore_errors: true register: inv_out @@ -12,3 +12,4 @@ that: - inv_out.stderr is contains("this is stderr") if item.script_name is undefined else true - inv_out.stderr is regex(item.expected_error) + - item.deprecation is undefined or inv_out.stderr is regex(item.deprecation) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/test_valid_inventory.yml new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/test_valid_inventory.yml --- old/ansible_core-2.19.2/test/integration/targets/inventory_script/tasks/test_valid_inventory.yml 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/inventory_script/tasks/test_valid_inventory.yml 2025-10-06 19:22:06.000000000 +0200 @@ -4,7 +4,6 @@ environment: ANSIBLE_INVENTORY_PLUGIN_SCRIPT_STDERR: '{{ item.show_stderr | default(omit) }}' ANSIBLE_DEPRECATION_WARNINGS: 1 # some tests assert deprecation warnings - INVENTORY_TEST_MODE: '{{ item.mode | default(omit) }}' INVENTORY_EMIT_STDERR: '{{ item.emit_stderr | default(omit) }}' register: inv_out diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/win_async_wrapper/library/trailing_output.ps1 new/ansible_core-2.19.3/test/integration/targets/win_async_wrapper/library/trailing_output.ps1 --- old/ansible_core-2.19.2/test/integration/targets/win_async_wrapper/library/trailing_output.ps1 1970-01-01 01:00:00.000000000 +0100 +++ new/ansible_core-2.19.3/test/integration/targets/win_async_wrapper/library/trailing_output.ps1 2025-10-06 19:22:06.000000000 +0200 @@ -0,0 +1,6 @@ +#!powershell + +#AnsibleRequires -Wrapper + +[Console]::Out.WriteLine('{"changed": false, "test": 123}') +'trailing junk after module result' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/integration/targets/win_async_wrapper/tasks/main.yml new/ansible_core-2.19.3/test/integration/targets/win_async_wrapper/tasks/main.yml --- old/ansible_core-2.19.2/test/integration/targets/win_async_wrapper/tasks/main.yml 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/integration/targets/win_async_wrapper/tasks/main.yml 2025-10-06 19:22:06.000000000 +0200 @@ -206,6 +206,21 @@ - not success_async_custom_dir_poll.failed - success_async_custom_dir_poll.results_file == win_output_dir + '\\' + async_custom_dir_poll.ansible_job_id +- name: test async with trailing output + trailing_output: + async: 10 + poll: 1 + register: async_trailing_output + +- name: assert test async with trailing output + assert: + that: + - async_trailing_output is not changed + - async_trailing_output.test == 123 + - async_trailing_output.warnings | count == 1 + - >- + async_trailing_output.warnings[0] is search('Module invocation had junk after the JSON data: trailing junk after module result') + # FUTURE: figure out why the last iteration of this test often fails on shippable #- name: loop async success # async_test: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/_internal/templating/test_jinja_bits.py new/ansible_core-2.19.3/test/units/_internal/templating/test_jinja_bits.py --- old/ansible_core-2.19.2/test/units/_internal/templating/test_jinja_bits.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/_internal/templating/test_jinja_bits.py 2025-10-06 19:22:06.000000000 +0200 @@ -9,6 +9,7 @@ import pytest import pytest_mock +from ansible._internal import _display_utils from ansible._internal._templating._access import NotifiableAccessContextBase from ansible.errors import AnsibleUndefinedVariable, AnsibleTemplateError from ansible._internal._templating._errors import AnsibleTemplatePluginRuntimeError @@ -22,8 +23,6 @@ from ansible._internal._templating._engine import TemplateEngine, TemplateOptions from jinja2.loaders import DictLoader -from ansible.utils.display import _DeferredWarningContext - if t.TYPE_CHECKING: import unittest.mock @@ -79,7 +78,7 @@ templar = TemplateEngine() templar.environment.loader = DictLoader(dict(foo=TRUST.tag('{{ undefined_in_import }}'))) - with _DeferredWarningContext(variables=templar.available_variables) as warnings: + with _display_utils.DeferredWarningContext(variables=templar.available_variables) as warnings: result = templar.template(template) assert not warnings.get_warnings() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/_internal/templating/test_templar.py new/ansible_core-2.19.3/test/units/_internal/templating/test_templar.py --- old/ansible_core-2.19.2/test/units/_internal/templating/test_templar.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/_internal/templating/test_templar.py 2025-10-06 19:22:06.000000000 +0200 @@ -31,6 +31,7 @@ import unittest +from ansible._internal import _display_utils from ansible._internal._templating._datatag import _JinjaConstTemplate from ansible.errors import ( AnsibleError, AnsibleUndefinedVariable, AnsibleTemplateSyntaxError, AnsibleBrokenConditionalError, AnsibleTemplateError, AnsibleTemplateTransformLimitError, @@ -52,7 +53,7 @@ from ansible._internal._templating._marker_behaviors import ReplacingMarkerBehavior from ansible._internal._templating._utils import TemplateContext from ansible.module_utils._internal import _event_utils -from ansible.utils.display import Display, _DeferredWarningContext +from ansible.utils.display import Display from units.mock.loader import DictDataLoader from units.test_utils.controller.display import emits_warnings @@ -1033,7 +1034,7 @@ templar = TemplateEngine(variables=variables) - with _DeferredWarningContext(variables=variables) as dwc: + with _display_utils.DeferredWarningContext(variables=variables) as dwc: # The indirect access summary occurs first. # The two following direct access summaries get deduped to a single one by the warning context (but unique template value keeps distinct from indirect). # The accesses with the shared tag instance values are internally deduped by the audit context. @@ -1049,14 +1050,14 @@ def test_jinja_const_template_leak(template_context: TemplateContext) -> None: """Verify that _JinjaConstTemplate is present during internal templating.""" - with _DeferredWarningContext(variables={}): # suppress warning from usage of embedded template + with _display_utils.DeferredWarningContext(variables={}): # suppress warning from usage of embedded template with unittest.mock.patch.object(_TemplateConfig, 'allow_embedded_templates', True): assert _JinjaConstTemplate.is_tagged_on(TemplateEngine().template(TRUST.tag("{{ '{{ 1 }}' }}"))) def test_jinja_const_template_finalized() -> None: """Verify that _JinjaConstTemplate is not present in finalized template results.""" - with _DeferredWarningContext(variables={}): # suppress warning from usage of embedded template + with _display_utils.DeferredWarningContext(variables={}): # suppress warning from usage of embedded template with unittest.mock.patch.object(_TemplateConfig, 'allow_embedded_templates', True): assert not _JinjaConstTemplate.is_tagged_on(TemplateEngine().template(TRUST.tag("{{ '{{ 1 }}' }}"))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/errors/test_utils.py new/ansible_core-2.19.3/test/units/errors/test_utils.py --- old/ansible_core-2.19.2/test/units/errors/test_utils.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/errors/test_utils.py 2025-10-06 19:22:06.000000000 +0200 @@ -5,9 +5,9 @@ from ansible._internal._errors import _error_factory from ansible.errors import AnsibleError +from ansible._internal import _display_utils from ansible._internal._datatag._tags import Origin from ansible._internal._errors._error_utils import format_exception_message -from ansible.utils.display import _format_message from ansible.module_utils._internal import _messages from units.mock.error_helper import raise_exceptions @@ -186,7 +186,7 @@ event = _error_factory.ControllerEventFactory.from_exception(error.value, False) message_chain = format_exception_message(error.value) - formatted_message = _format_message(_messages.ErrorSummary(event=event), False) + formatted_message = _display_utils.format_message(_messages.ErrorSummary(event=event), False) assert message_chain == expected_message_chain assert formatted_message.strip() == (expected_formatted_message or expected_message_chain) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/parsing/yaml/test_objects.py new/ansible_core-2.19.3/test/units/parsing/yaml/test_objects.py --- old/ansible_core-2.19.2/test/units/parsing/yaml/test_objects.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/parsing/yaml/test_objects.py 2025-10-06 19:22:06.000000000 +0200 @@ -4,17 +4,17 @@ import pytest +from ansible._internal import _display_utils from ansible._internal._datatag._tags import Origin from ansible.module_utils._internal._datatag import AnsibleTagHelper from ansible.parsing.vault import EncryptedString from ansible.parsing.yaml.objects import _AnsibleMapping, _AnsibleUnicode, _AnsibleSequence -from ansible.utils.display import _DeferredWarningContext from ansible.parsing.yaml import objects @pytest.fixture(autouse=True, scope='function') def suppress_warnings() -> t.Generator[None]: - with _DeferredWarningContext(variables={}): + with _display_utils.DeferredWarningContext(variables={}): yield diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/test_utils/controller/display.py new/ansible_core-2.19.3/test/units/test_utils/controller/display.py --- old/ansible_core-2.19.2/test/units/test_utils/controller/display.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/test_utils/controller/display.py 2025-10-06 19:22:06.000000000 +0200 @@ -4,8 +4,8 @@ import re import typing as t +from ansible._internal import _display_utils from ansible.module_utils._internal import _messages -from ansible.utils.display import _DeferredWarningContext @contextlib.contextmanager @@ -16,7 +16,7 @@ allow_unmatched_message: bool = False, ) -> t.Iterator[None]: """Assert that the code within the context manager body emits a warning or deprecation warning whose formatted output matches the supplied regex.""" - with _DeferredWarningContext(variables=dict(ansible_deprecation_warnings=True)) as ctx: + with _display_utils.DeferredWarningContext(variables=dict(ansible_deprecation_warnings=True)) as ctx: yield deprecations = ctx.get_deprecation_warnings() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ansible_core-2.19.2/test/units/utils/test_display.py new/ansible_core-2.19.3/test/units/utils/test_display.py --- old/ansible_core-2.19.2/test/units/utils/test_display.py 2025-09-08 20:08:07.000000000 +0200 +++ new/ansible_core-2.19.3/test/units/utils/test_display.py 2025-10-06 19:22:06.000000000 +0200 @@ -16,8 +16,9 @@ from ansible.module_utils.datatag import deprecator_from_collection_name from ansible.module_utils._internal import _deprecator, _errors, _messages -from ansible.utils.display import _LIBC, _MAX_INT, Display, get_text_width, _format_message +from ansible.utils.display import _LIBC, _MAX_INT, Display, get_text_width from ansible.utils.multiprocessing import context as multiprocessing_context +from ansible._internal import _display_utils @pytest.fixture @@ -164,7 +165,7 @@ ), ) - result = _format_message(_messages.DeprecationSummary(event=event), False) + result = _display_utils.format_message(_messages.DeprecationSummary(event=event), False) assert result == '''Ignoring ExceptionX. This feature will be removed in the future: Something went wrong. @@ -236,7 +237,7 @@ for kwarg in ('version', 'date', 'deprecator'): kwargs.setdefault(kwarg, None) - msg = Display()._get_deprecation_message_with_plugin_info(**kwargs) + msg = _display_utils.get_deprecation_message_with_plugin_info(**kwargs) assert msg == expected ++++++ ansible_core-2.19.2.tar.gz.sha256 -> ansible_core-2.19.3.tar.gz.sha256 ++++++ --- /work/SRC/openSUSE:Factory/ansible-core/ansible_core-2.19.2.tar.gz.sha256 2025-09-22 16:41:31.494062937 +0200 +++ /work/SRC/openSUSE:Factory/.ansible-core.new.11973/ansible_core-2.19.3.tar.gz.sha256 2025-10-07 18:30:04.145769808 +0200 @@ -1 +1 @@ -87fcbbc492ed16eb6adb0379bae0adbf69f3ce88a8440e7e88e0dcefa9f8a54c ansible_core-2.19.2.tar.gz +243a69669a007be0794360bc4477f70e0128ce0091dc3af4c5cb81c6a466f573 ansible_core-2.19.3.tar.gz
