From 71ef9811919a6fca005e6eff447b4955ff649775 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Tue, 8 Dec 2020 13:07:29 +1300 Subject: [PATCH] Backport/2.10/72390 (#72690) * Return error if cwd directory does not exist (#72390) * Return warning or error if cwd directory does not exist, in AnsibleModule.run_command() (cherry picked from commit 5654de6fceeabb190111d5fb5d3e092a7e5d7f3b) * added flag in run_command signature to control behaviour when cwd does not exist --- ...-error-if-cwd-directory-does-not-exist.yml | 2 ++ changelogs/fragments/run-command-cwd.yml | 2 ++ lib/ansible/module_utils/basic.py | 24 ++++++++++++------- .../module_utils/basic/test_run_command.py | 8 +++++++ 4 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 changelogs/fragments/72390-return-error-if-cwd-directory-does-not-exist.yml create mode 100644 changelogs/fragments/run-command-cwd.yml diff --git a/changelogs/fragments/72390-return-error-if-cwd-directory-does-not-exist.yml b/changelogs/fragments/72390-return-error-if-cwd-directory-does-not-exist.yml new file mode 100644 index 00000000000..75d48a6541d --- /dev/null +++ b/changelogs/fragments/72390-return-error-if-cwd-directory-does-not-exist.yml @@ -0,0 +1,2 @@ +bugfixes: + - basic.AnsibleModule - AnsibleModule.run_command silently ignores a non-existent directory in the ``cwd`` argument (https://github.com/ansible/ansible/pull/72390). diff --git a/changelogs/fragments/run-command-cwd.yml b/changelogs/fragments/run-command-cwd.yml new file mode 100644 index 00000000000..705ecdd8566 --- /dev/null +++ b/changelogs/fragments/run-command-cwd.yml @@ -0,0 +1,2 @@ +bugfixes: + - AnsibleModule - added arg ``ignore_invalid_cwd`` to ``AnsibleModule.run_command()``, to control its behaviour when ``cwd`` is invalid. (https://github.com/ansible/ansible/pull/72390) diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 7b8b5879652..24d225f5c17 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -2531,7 +2531,7 @@ class AnsibleModule(object): def run_command(self, args, check_rc=False, close_fds=True, executable=None, data=None, binary_data=False, path_prefix=None, cwd=None, use_unsafe_shell=False, prompt_regex=None, environ_update=None, umask=None, encoding='utf-8', errors='surrogate_or_strict', - expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None): + expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None, ignore_invalid_cwd=True): ''' Execute a command, returns rc, stdout, and stderr. @@ -2582,6 +2582,9 @@ class AnsibleModule(object): after ``Popen`` object will be created but before communicating to the process. (``Popen`` object will be passed to callback as a first argument) + :kw ignore_invalid_cwd: This flag indicates whether an invalid ``cwd`` + (non-existent or not a directory) should be ignored or should raise + an exception. :returns: A 3-tuple of return code (integer), stdout (native string), and stderr (native string). On python2, stdout and stderr are both byte strings. On python3, stdout and stderr are text strings converted @@ -2695,14 +2698,17 @@ class AnsibleModule(object): prev_dir = os.getcwd() # make sure we're in the right working directory - if cwd and os.path.isdir(cwd): - cwd = to_bytes(os.path.abspath(os.path.expanduser(cwd)), errors='surrogate_or_strict') - kwargs['cwd'] = cwd - try: - os.chdir(cwd) - except (OSError, IOError) as e: - self.fail_json(rc=e.errno, msg="Could not open %s, %s" % (cwd, to_native(e)), - exception=traceback.format_exc()) + if cwd: + if os.path.isdir(cwd): + cwd = to_bytes(os.path.abspath(os.path.expanduser(cwd)), errors='surrogate_or_strict') + kwargs['cwd'] = cwd + try: + os.chdir(cwd) + except (OSError, IOError) as e: + self.fail_json(rc=e.errno, msg="Could not chdir to %s, %s" % (cwd, to_native(e)), + exception=traceback.format_exc()) + elif not ignore_invalid_cwd: + self.fail_json(msg="Provided cwd is not a valid directory: %s" % cwd) old_umask = None if umask: diff --git a/test/units/module_utils/basic/test_run_command.py b/test/units/module_utils/basic/test_run_command.py index 3fc9b1f72e8..25f1c48ed99 100644 --- a/test/units/module_utils/basic/test_run_command.py +++ b/test/units/module_utils/basic/test_run_command.py @@ -180,6 +180,14 @@ class TestRunCommandCwd: rc_am.run_command('/bin/ls', cwd='/not-a-dir') assert rc_am._os.chdir.mock_calls == [mocker.call('/old'), ] + @pytest.mark.parametrize('stdin', [{}], indirect=['stdin']) + def test_cwd_not_a_dir_noignore(self, rc_am): + rc_am._os.getcwd.return_value = '/old' + rc_am._os.path.isdir.side_effect = lambda d: d != '/not-a-dir' + with pytest.raises(SystemExit): + rc_am.run_command('/bin/ls', cwd='/not-a-dir', ignore_invalid_cwd=False) + assert rc_am.fail_json.called + class TestRunCommandPrompt: @pytest.mark.parametrize('stdin', [{}], indirect=['stdin'])