From 7ebc9fa5d60d4c4593979f3f9ca40875260d73ff Mon Sep 17 00:00:00 2001 From: Sloane Hertel <19572925+s-hertel@users.noreply.github.com> Date: Tue, 18 Nov 2025 20:04:22 -0500 Subject: [PATCH] Support configuring callback plugins with --extra-vars (#84661) * Support configuring callback plugins with --extra-vars Callback plugins define variable names in the documentation for ConfigManager Variable values can be omitted * Added default callback variable configuration for display_skipped_hosts Fixes #84469 Co-authored-by: Matt Clay --- .../configure_display_skipped_hosts.yml | 3 +++ lib/ansible/executor/task_queue_manager.py | 17 +++++++++++++++-- .../plugins/doc_fragments/default_callback.py | 3 +++ .../targets/callback_results/callback_vars.yml | 2 ++ .../targets/callback_results/runme.sh | 17 +++++++++++++++++ .../targets/callback_results/skip_hosts.yml | 5 +++++ .../testcoll/plugins/callback/usercallback.py | 7 ++++++- test/integration/targets/collections/runme.sh | 3 +++ 8 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/configure_display_skipped_hosts.yml create mode 100644 test/integration/targets/callback_results/callback_vars.yml create mode 100644 test/integration/targets/callback_results/skip_hosts.yml diff --git a/changelogs/fragments/configure_display_skipped_hosts.yml b/changelogs/fragments/configure_display_skipped_hosts.yml new file mode 100644 index 00000000000..457ebcef82d --- /dev/null +++ b/changelogs/fragments/configure_display_skipped_hosts.yml @@ -0,0 +1,3 @@ +minor_changes: + - callback plugins - support configuration using extra variables. + - default callback plugin - add variable configuration for ``display_skipped_hosts`` (https://github.com/ansible/ansible/issues/84469). diff --git a/lib/ansible/executor/task_queue_manager.py b/lib/ansible/executor/task_queue_manager.py index 6c008be5e2b..644ff963d3f 100644 --- a/lib/ansible/executor/task_queue_manager.py +++ b/lib/ansible/executor/task_queue_manager.py @@ -40,6 +40,7 @@ from ansible.module_utils.common.text.converters import to_native from ansible.parsing.dataloader import DataLoader from ansible.playbook.play_context import PlayContext from ansible.playbook.task import Task +from ansible.plugins.callback import CallbackBase from ansible.plugins.loader import callback_loader, strategy_loader, module_loader from ansible.plugins.callback import CallbackBase from ansible._internal._templating._engine import TemplateEngine @@ -111,6 +112,16 @@ class AnsibleEndPlay(Exception): self.result = result +def _resolve_callback_option_variables(callback: CallbackBase, variables: dict[str, object], templar: TemplateEngine) -> None: + """Set callback plugin options using documented variables.""" + callback_variables = { + var_name: variables[var_name] + for var_name in C.config.get_plugin_vars(callback.plugin_type, callback._load_name) + if var_name in variables + } + callback.set_options(var_options=templar.template(callback_variables)) + + class TaskQueueManager: """ @@ -248,8 +259,10 @@ class TaskQueueManager: if not stdout_callback: raise AnsibleError(f"Could not load {self._stdout_callback_name!r} callback plugin.") + templar = TemplateEngine(loader=self._loader, variables=self._variable_manager._extra_vars) + stdout_callback._init_callback_methods() - stdout_callback.set_options() + _resolve_callback_option_variables(stdout_callback, self._variable_manager._extra_vars, templar) self._callback_plugins.append(stdout_callback) @@ -303,7 +316,7 @@ class TaskQueueManager: # really a bug in the plugin itself which we ignore as callback errors are not supposed to be fatal. if callback_obj: callback_obj._init_callback_methods() - callback_obj.set_options() + _resolve_callback_option_variables(callback_obj, self._variable_manager._extra_vars, templar) self._callback_plugins.append(callback_obj) else: display.warning("Skipping callback '%s', as it does not create a valid plugin instance." % callback_name) diff --git a/lib/ansible/plugins/doc_fragments/default_callback.py b/lib/ansible/plugins/doc_fragments/default_callback.py index 7c02c290b9f..228fa168a1e 100644 --- a/lib/ansible/plugins/doc_fragments/default_callback.py +++ b/lib/ansible/plugins/doc_fragments/default_callback.py @@ -19,6 +19,9 @@ class ModuleDocFragment(object): ini: - key: display_skipped_hosts section: defaults + vars: + - name: ansible_display_skipped_hosts + version_added: "2.21" display_ok_hosts: name: Show 'ok' hosts description: "Toggle to control displaying 'ok' task/host results in a task." diff --git a/test/integration/targets/callback_results/callback_vars.yml b/test/integration/targets/callback_results/callback_vars.yml new file mode 100644 index 00000000000..abf9eb2b730 --- /dev/null +++ b/test/integration/targets/callback_results/callback_vars.yml @@ -0,0 +1,2 @@ +indirect_extra: false +ansible_display_skipped_hosts: "{{ indirect_extra }}" diff --git a/test/integration/targets/callback_results/runme.sh b/test/integration/targets/callback_results/runme.sh index 283c0b210df..44aa481e4c0 100755 --- a/test/integration/targets/callback_results/runme.sh +++ b/test/integration/targets/callback_results/runme.sh @@ -23,3 +23,20 @@ fi # test connection tracking ANSIBLE_CALLBACKS_ENABLED=track_connections ansible-playbook "$@" -i ../../inventory connection_name.yml | tee "${OUTFILE}" grep "FOUND EXPECTED EVENTS" "${OUTFILE}" + +# test configuring display_skipped_hosts using --extra-vars +hide_skipped="$(ansible-playbook skip_hosts.yml -v --extra-vars @./callback_vars.yml "$@")" +if [[ "$hide_skipped" == *skip_reason* ]]; then + echo "Failed to configure display_skipped_hosts (false)" + exit 1 +fi +include_skipped="$(ansible-playbook skip_hosts.yml -v --extra-vars @./callback_vars.yml --extra-vars indirect_extra=true "$@")" +if [[ "$include_skipped" != *skip_reason* ]]; then + echo "Failed to configure display_skipped_hosts (true)" + exit 1 +fi +include_skipped="$(ansible-playbook skip_hosts.yml -v --extra-vars @./callback_vars.yml --extra-vars indirect_extra='{{ omit }}' "$@")" +if [[ "$include_skipped" != *skip_reason* ]]; then + echo "Failed to omit display_skipped_hosts (default true)" + exit 1 +fi diff --git a/test/integration/targets/callback_results/skip_hosts.yml b/test/integration/targets/callback_results/skip_hosts.yml new file mode 100644 index 00000000000..ff43563d523 --- /dev/null +++ b/test/integration/targets/callback_results/skip_hosts.yml @@ -0,0 +1,5 @@ +- hosts: localhost + gather_facts: false + tasks: + - command: /bin/false + when: false diff --git a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py index 3800dc8f7b8..2d010e1fcb7 100644 --- a/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py +++ b/test/integration/targets/collections/collection_root_user/ansible_collections/testns/testcoll/plugins/callback/usercallback.py @@ -8,6 +8,11 @@ DOCUMENTATION = """ short_description: does stuff description: - does some stuff + options: + ok_msg: + default: "usercallback says ok" + vars: + - name: on_ok """ @@ -22,4 +27,4 @@ class CallbackModule(CallbackBase): self._display.display("loaded usercallback from collection, yay") def v2_runner_on_ok(self, result): - self._display.display("usercallback says ok") + self._display.display(self.get_option("ok_msg")) diff --git a/test/integration/targets/collections/runme.sh b/test/integration/targets/collections/runme.sh index 034f5e7e0ce..a678a39e773 100755 --- a/test/integration/targets/collections/runme.sh +++ b/test/integration/targets/collections/runme.sh @@ -31,6 +31,9 @@ ANSIBLE_CALLBACKS_ENABLED=formerly_core_removed_callback ansible localhost -m de # ensure non existing callback does not crash ansible ANSIBLE_CALLBACKS_ENABLED=charlie.gomez.notme ansible localhost -m debug 2>&1 | grep -- "Skipping callback plugin 'charlie.gomez.notme'" +# test configuring collection callback plugins with extra variables +ANSIBLE_CALLBACKS_ENABLED=testns.testcoll.usercallback ansible-playbook noop.yml --extra-vars 'on_ok="configured msg"' | grep 'configured msg' + unset ANSIBLE_LOAD_CALLBACK_PLUGINS # adhoc normally shouldn't load non-default plugins- let's be sure output=$(ANSIBLE_CALLBACKS_ENABLED=testns.testcoll.usercallback ansible localhost -m debug)