From db4973572a2cfce3fb738c52e8a55e623d1cedb5 Mon Sep 17 00:00:00 2001 From: Matt Davis <6775756+nitzmahone@users.noreply.github.com> Date: Tue, 5 Aug 2025 12:12:47 -0700 Subject: [PATCH] Add temporary module result serialization hook (#85609) (#85621) * Add temporary module result serialization hook * Sanity test fix --------- (cherry picked from commit faf86ca2b3e7b06660528d69a9839bb8d1409f70) Co-authored-by: Matt Clay --- changelogs/fragments/module_direct_exec.yml | 2 ++ lib/ansible/module_utils/basic.py | 12 +++++++-- .../module_utils/basic/test_exit_json.py | 25 ++++++++++++++++++- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/module_direct_exec.yml diff --git a/changelogs/fragments/module_direct_exec.yml b/changelogs/fragments/module_direct_exec.yml new file mode 100644 index 00000000000..edc407023d8 --- /dev/null +++ b/changelogs/fragments/module_direct_exec.yml @@ -0,0 +1,2 @@ +minor_changes: + - AnsibleModule - Add temporary internal monkeypatch-able hook to alter module result serialization by splitting serialization from ``_return_formatted`` into ``_record_module_result``. diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index b6104396ded..5b5a05e8efc 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -1512,11 +1512,19 @@ class AnsibleModule(object): # strip no_log collisions kwargs = remove_values(kwargs, self.no_log_values) - # return preserved + # graft preserved values back on kwargs.update(preserved) + self._record_module_result(kwargs) + + def _record_module_result(self, o: dict[str, t.Any]) -> None: + """ + Temporary internal hook to enable modification/bypass of module result serialization. + + Monkeypatched by ansible.netcommon for direct in-worker module execution. + """ encoder = _json.get_module_encoder(_ANSIBLE_PROFILE, _json.Direction.MODULE_TO_CONTROLLER) - print('\n%s' % json.dumps(kwargs, cls=encoder)) + print('\n%s' % json.dumps(o, cls=encoder)) def exit_json(self, **kwargs) -> t.NoReturn: """ return from the module, without error """ diff --git a/test/units/module_utils/basic/test_exit_json.py b/test/units/module_utils/basic/test_exit_json.py index 47d96d3c1d6..f010e56f630 100644 --- a/test/units/module_utils/basic/test_exit_json.py +++ b/test/units/module_utils/basic/test_exit_json.py @@ -10,7 +10,7 @@ import datetime import typing as t import pytest - +import pytest_mock EMPTY_INVOCATION: dict[str, dict[str, t.Any]] = {u'module_args': {}} DATETIME = datetime.datetime.strptime('2020-07-13 12:50:00', '%Y-%m-%d %H:%M:%S') @@ -153,3 +153,26 @@ class TestAnsibleModuleExitValuesRemoved: out, err = capfd.readouterr() assert json.loads(out) == expected + + def test_record_module_result(self, mocker: pytest_mock.MockerFixture, stdin) -> None: + """Ensure that the temporary _record_module_result hook is called correctly.""" + recorded_result = None + + expected_result = dict(changed=False, worked="yay") + + def _record_module_result(_self, o: object) -> None: + assert isinstance(o, dict) + + nonlocal recorded_result + recorded_result = o + + from ansible.module_utils.basic import AnsibleModule + + mocker.patch.object(AnsibleModule, '_record_module_result', _record_module_result) + + am = AnsibleModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + am.exit_json(**expected_result) + + assert expected_result.items() <= recorded_result.items()