fix(tasks: synchronize): wrap in sshpass if ssh password was provided (#30743)

* fix(tasks: synchronize): wrap in sshpass if ssh password was provided

Closes #16616

* fix(tasks: synchronize): pass rsync password to sshpass via fd

* fix(tasks: synchronize): use fail_json instead of AnsibleError

* fixup! fix(tasks: synchronize): use fail_json instead of AnsibleError

fix python2 handling

* feat(module_utils: basic: run_command): add optional arguments `pass_fds` and `before_communicate_callback`

* fix(tasks: synchronize): use module.run_command instead of subprocess.Popen

* fixup! fix(tasks: synchronize): use module.run_command instead of subprocess.Popen

remove unused import

* fixup! fixup! fix(tasks: synchronize): use module.run_command instead of subprocess.Popen

pass_fds only if they passed to run_command()
pull/48125/head
Yauhen Kirylau 6 years ago committed by ansibot
parent 7f3c21f628
commit 14037443de

@ -2695,7 +2695,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, 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', use_unsafe_shell=False, prompt_regex=None, environ_update=None, umask=None, encoding='utf-8', errors='surrogate_or_strict',
expand_user_and_vars=True): expand_user_and_vars=True, pass_fds=None, before_communicate_callback=None):
''' '''
Execute a command, returns rc, stdout, and stderr. Execute a command, returns rc, stdout, and stderr.
@ -2738,6 +2738,13 @@ class AnsibleModule(object):
are expanded before running the command. When ``True`` a string such as are expanded before running the command. When ``True`` a string such as
``$SHELL`` will be expanded regardless of escaping. When ``False`` and ``$SHELL`` will be expanded regardless of escaping. When ``False`` and
``use_unsafe_shell=False`` no path or variable expansion will be done. ``use_unsafe_shell=False`` no path or variable expansion will be done.
:kw pass_fds: When running on python3 this argument
dictates which file descriptors should be passed
to an underlying ``Popen`` constructor.
:kw before_communicate_callback: This function will be called
after ``Popen`` object will be created
but before communicating to the process.
(``Popen`` object will be passed to callback as a first argument)
:returns: A 3-tuple of return code (integer), stdout (native string), :returns: A 3-tuple of return code (integer), stdout (native string),
and stderr (native string). On python2, stdout and stderr are both and stderr (native string). On python2, stdout and stderr are both
byte strings. On python3, stdout and stderr are text strings converted byte strings. On python3, stdout and stderr are text strings converted
@ -2839,6 +2846,8 @@ class AnsibleModule(object):
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
preexec_fn=self._restore_signal_handlers, preexec_fn=self._restore_signal_handlers,
) )
if PY3 and pass_fds:
kwargs["pass_fds"] = pass_fds
# store the pwd # store the pwd
prev_dir = os.getcwd() prev_dir = os.getcwd()
@ -2861,6 +2870,8 @@ class AnsibleModule(object):
if self._debug: if self._debug:
self.log('Executing: ' + self._clean_args(args)) self.log('Executing: ' + self._clean_args(args))
cmd = subprocess.Popen(args, **kwargs) cmd = subprocess.Popen(args, **kwargs)
if before_communicate_callback:
before_communicate_callback(cmd)
# the communication logic here is essentially taken from that # the communication logic here is essentially taken from that
# of the _communicate() function in ssh.py # of the _communicate() function in ssh.py

@ -319,8 +319,9 @@ EXAMPLES = '''
import os import os
import errno
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule, to_bytes
from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils.six.moves import shlex_quote
@ -365,6 +366,7 @@ def main():
private_key=dict(type='path'), private_key=dict(type='path'),
rsync_path=dict(type='str'), rsync_path=dict(type='str'),
_local_rsync_path=dict(type='path', default='rsync'), _local_rsync_path=dict(type='path', default='rsync'),
_local_rsync_password=dict(default=None, no_log=True),
_substitute_controller=dict(type='bool', default=False), _substitute_controller=dict(type='bool', default=False),
archive=dict(type='bool', default=True), archive=dict(type='bool', default=True),
checksum=dict(type='bool', default=False), checksum=dict(type='bool', default=False),
@ -404,6 +406,7 @@ def main():
private_key = module.params['private_key'] private_key = module.params['private_key']
rsync_path = module.params['rsync_path'] rsync_path = module.params['rsync_path']
rsync = module.params.get('_local_rsync_path', 'rsync') rsync = module.params.get('_local_rsync_path', 'rsync')
rsync_password = module.params.get('_local_rsync_password')
rsync_timeout = module.params.get('rsync_timeout', 'rsync_timeout') rsync_timeout = module.params.get('rsync_timeout', 'rsync_timeout')
archive = module.params['archive'] archive = module.params['archive']
checksum = module.params['checksum'] checksum = module.params['checksum']
@ -428,6 +431,16 @@ def main():
rsync = module.get_bin_path(rsync, required=True) rsync = module.get_bin_path(rsync, required=True)
cmd = [rsync, '--delay-updates', '-F'] cmd = [rsync, '--delay-updates', '-F']
_sshpass_pipe = None
if rsync_password:
try:
module.run_command(["sshpass"])
except OSError:
module.fail_json(
msg="to use rsync connection with passwords, you must install the sshpass program"
)
_sshpass_pipe = os.pipe()
cmd = ['sshpass', '-d' + _sshpass_pipe[0]] + cmd
if compress: if compress:
cmd.append('--compress') cmd.append('--compress')
if rsync_timeout: if rsync_timeout:
@ -534,7 +547,24 @@ def main():
cmd.append(source) cmd.append(source)
cmd.append(dest) cmd.append(dest)
cmdstr = ' '.join(cmd) cmdstr = ' '.join(cmd)
(rc, out, err) = module.run_command(cmd)
# If we are using password authentication, write the password into the pipe
if rsync_password:
def _write_password_to_pipe(proc):
os.close(_sshpass_pipe[0])
try:
os.write(_sshpass_pipe[1], to_bytes(rsync_password) + b'\n')
except OSError as exc:
# Ignore broken pipe errors if the sshpass process has exited.
if exc.errno != errno.EPIPE or proc.poll() is None:
raise
(rc, out, err) = module.run_command(
cmd, pass_fds=_sshpass_pipe,
before_communicate_callback=_write_password_to_pipe
)
else:
(rc, out, err) = module.run_command(cmd)
if rc: if rc:
return module.fail_json(msg=err, rc=rc, cmd=cmdstr) return module.fail_json(msg=err, rc=rc, cmd=cmdstr)

@ -205,6 +205,7 @@ class ActionModule(ActionBase):
# Parameter name needed by the ansible module # Parameter name needed by the ansible module
_tmp_args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync' _tmp_args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync'
_tmp_args['_local_rsync_password'] = task_vars.get('ansible_ssh_pass') or task_vars.get('ansible_password')
# rsync thinks that one end of the connection is localhost and the # rsync thinks that one end of the connection is localhost and the
# other is the host we're running the task for (Note: We use # other is the host we're running the task for (Note: We use

Loading…
Cancel
Save