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()
This commit is contained in:
parent
7f3c21f628
commit
14037443de
3 changed files with 45 additions and 3 deletions
|
@ -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…
Add table
Reference in a new issue