diff --git a/changelogs/fragments/56629-synchronize-password-auth.yaml b/changelogs/fragments/56629-synchronize-password-auth.yaml new file mode 100644 index 00000000000..1f888c0177e --- /dev/null +++ b/changelogs/fragments/56629-synchronize-password-auth.yaml @@ -0,0 +1,6 @@ +bugfixes: + - synchronize - fix password authentication on Python 2 (https://github.com/ansible/ansible/issues/56629) + - > + AnsibleModule.run_command() - set ``close_fds`` to ``False`` on Python 2 if ``pass_fds`` are passed to + ``run_command()``. Since ``subprocess.Popen()`` on Python 2 does not have the ``pass_fds`` option, + there is no way to exclude a specific list of file descriptors from being closed. diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 4756381ac6d..b3f580a4f2e 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -2452,9 +2452,10 @@ class AnsibleModule(object): are expanded before running the command. When ``True`` a string such as ``$SHELL`` will be expanded regardless of escaping. When ``False`` and ``use_unsafe_shell=False`` no path or variable expansion will be done. - :kw pass_fds: When running on python3 this argument + :kw pass_fds: When running on Python 3 this argument dictates which file descriptors should be passed - to an underlying ``Popen`` constructor. + to an underlying ``Popen`` constructor. On Python 2, this will + set ``close_fds`` to False. :kw before_communicate_callback: This function will be called after ``Popen`` object will be created but before communicating to the process. @@ -2565,6 +2566,8 @@ class AnsibleModule(object): ) if PY3 and pass_fds: kwargs["pass_fds"] = pass_fds + elif PY2 and pass_fds: + kwargs['close_fds'] = False # store the pwd prev_dir = os.getcwd() diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py index e41a4960b45..3e5e874ad63 100644 --- a/test/units/module_utils/basic/test_run_command.py +++ b/test/units/module_utils/basic/test_run_command.py @@ -13,6 +13,7 @@ from io import BytesIO import pytest from ansible.module_utils._text import to_native +from ansible.module_utils.six import PY2 class OpenBytesIO(BytesIO): @@ -197,3 +198,22 @@ class TestRunCommandOutput: # bytes because it's returning native strings assert stdout == to_native(u'Žarn§') assert stderr == to_native(u'لرئيسية') + + +@pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) +def test_run_command_fds(mocker, rc_am): + subprocess_mock = mocker.patch('ansible.module_utils.basic.subprocess') + subprocess_mock.Popen.side_effect = AssertionError + + try: + rc_am.run_command('synchronize', pass_fds=(101, 42)) + except SystemExit: + pass + + if PY2: + assert subprocess_mock.Popen.call_args[1]['close_fds'] is False + assert 'pass_fds' not in subprocess_mock.Popen.call_args[1] + + else: + assert subprocess_mock.Popen.call_args[1]['pass_fds'] == (101, 42) + assert subprocess_mock.Popen.call_args[1]['close_fds'] is True