rework run_command's env setting to not change os.environ for the rest of the module.

New param to run_command to modify the environment for just this invocation.
Documentation and comment adjustments.
pull/14039/head
Toshio Kuratomi 9 years ago
parent cf75521bc8
commit 63404821da

@ -544,11 +544,10 @@ class AnsibleModule(object):
if no_log_object: if no_log_object:
self.no_log_values.update(return_values(no_log_object)) self.no_log_values.update(return_values(no_log_object))
# check the locale as set by the current environment, and # check the locale as set by the current environment, and reset to
# reset to LANG=C if it's an invalid/unavailable locale # a known valid (LANG=C) if it's an invalid/unavailable locale
self._check_locale() self._check_locale()
self._check_arguments(check_invalid_arguments) self._check_arguments(check_invalid_arguments)
# check exclusive early # check exclusive early
@ -1057,7 +1056,6 @@ class AnsibleModule(object):
# as it would be returned by locale.getdefaultlocale() # as it would be returned by locale.getdefaultlocale()
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
except locale.Error: except locale.Error:
e = get_exception()
# fallback to the 'C' locale, which may cause unicode # fallback to the 'C' locale, which may cause unicode
# issues but is preferable to simply failing because # issues but is preferable to simply failing because
# of an unknown locale # of an unknown locale
@ -1714,25 +1712,29 @@ class AnsibleModule(object):
# rename might not preserve context # rename might not preserve context
self.set_context_if_different(dest, context, False) self.set_context_if_different(dest, context, False)
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): 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):
''' '''
Execute a command, returns rc, stdout, and stderr. Execute a command, returns rc, stdout, and stderr.
args is the command to run
If args is a list, the command will be run with shell=False. :arg args: is the command to run
If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False * If args is a list, the command will be run with shell=False.
If args is a string and use_unsafe_shell=True it run with shell=True. * If args is a string and use_unsafe_shell=False it will split args to a list and run with shell=False
Other arguments: * If args is a string and use_unsafe_shell=True it runs with shell=True.
- check_rc (boolean) Whether to call fail_json in case of :kw check_rc: Whether to call fail_json in case of non zero RC.
non zero RC. Default is False. Default False
- close_fds (boolean) See documentation for subprocess.Popen(). :kw close_fds: See documentation for subprocess.Popen(). Default True
Default is True. :kw executable: See documentation for subprocess.Popen(). Default None
- executable (string) See documentation for subprocess.Popen(). :kw data: If given, information to write to the stdin of the command
Default is None. :kw binary_data: If False, append a newline to the data. Default False
- prompt_regex (string) A regex string (not a compiled regex) which :kw path_prefix: If given, additional path to find the command in.
can be used to detect prompts in the stdout This adds to the PATH environment vairable so helper commands in
which would otherwise cause the execution the same directory can also be found
to hang (especially if no input data is :kw cwd: iIf given, working directory to run the command inside
specified) :kw use_unsafe_shell: See `args` parameter. Default False
:kw prompt_regex: Regex string (not a compiled regex) which can be
used to detect prompts in the stdout which would otherwise cause
the execution to hang (especially if no input data is specified)
:kwarg environ_update: dictionary to *update* os.environ with
''' '''
shell = False shell = False
@ -1763,10 +1765,15 @@ class AnsibleModule(object):
msg = None msg = None
st_in = None st_in = None
# Set a temporary env path if a prefix is passed # Manipulate the environ we'll send to the new process
env=os.environ old_env_vals = {}
if environ_update:
for key, val in environ_update.items():
old_env_vals[key] = os.environ.get(key, None)
os.environ[key] = val
if path_prefix: if path_prefix:
env['PATH']="%s:%s" % (path_prefix, env['PATH']) old_env_vals['PATH'] = os.environ['PATH']
os.environ['PATH'] = "%s:%s" % (path_prefix, os.environ['PATH'])
# create a printable version of the command for use # create a printable version of the command for use
# in reporting later, which strips out things like # in reporting later, which strips out things like
@ -1808,11 +1815,10 @@ class AnsibleModule(object):
close_fds=close_fds, close_fds=close_fds,
stdin=st_in, stdin=st_in,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE stderr=subprocess.PIPE,
env=os.environ,
) )
if path_prefix:
kwargs['env'] = env
if cwd and os.path.isdir(cwd): if cwd and os.path.isdir(cwd):
kwargs['cwd'] = cwd kwargs['cwd'] = cwd
@ -1891,6 +1897,13 @@ class AnsibleModule(object):
except: except:
self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args) self.fail_json(rc=257, msg=traceback.format_exc(), cmd=clean_args)
# Restore env settings
for key, val in old_env_vals.items():
if val is None:
del os.environ[key]
else:
os.environ[key] = val
if rc != 0 and check_rc: if rc != 0 and check_rc:
msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values) msg = heuristic_log_sanitize(stderr.rstrip(), self.no_log_values)
self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg) self.fail_json(cmd=clean_args, rc=rc, stdout=stdout, stderr=stderr, msg=msg)

@ -39,6 +39,7 @@ class OpenStringIO(StringIO):
def close(self): def close(self):
pass pass
@unittest.skipIf(sys.version_info[0] >= 3, "Python 3 is not supported on targets (yet)") @unittest.skipIf(sys.version_info[0] >= 3, "Python 3 is not supported on targets (yet)")
class TestAnsibleModuleRunCommand(unittest.TestCase): class TestAnsibleModuleRunCommand(unittest.TestCase):
@ -111,10 +112,6 @@ class TestAnsibleModuleRunCommand(unittest.TestCase):
self.assertEqual(args, ('ls a " b" "c "', )) self.assertEqual(args, ('ls a " b" "c "', ))
self.assertEqual(kwargs['shell'], True) self.assertEqual(kwargs['shell'], True)
def test_path_prefix(self):
self.module.run_command('foo', path_prefix='/opt/bin')
self.assertEqual('/opt/bin', self.os.environ['PATH'].split(':')[0])
def test_cwd(self): def test_cwd(self):
self.os.getcwd.return_value = '/old' self.os.getcwd.return_value = '/old'
self.module.run_command('/bin/ls', cwd='/new') self.module.run_command('/bin/ls', cwd='/new')

Loading…
Cancel
Save