allow configuring sftp/scp executables (#36648)
* allow configuring sftp/scp executables fixes #36616 also removed dupe test
This commit is contained in:
parent
e7db3c0eba
commit
4aac0f5f18
2 changed files with 30 additions and 31 deletions
|
@ -65,9 +65,24 @@ DOCUMENTATION = '''
|
|||
env: [{name: ANSIBLE_SSH_EXECUTABLE}]
|
||||
ini:
|
||||
- {key: ssh_executable, section: ssh_connection}
|
||||
yaml: {key: ssh_connection.ssh_executable}
|
||||
#const: ANSIBLE_SSH_EXECUTABLE
|
||||
version_added: "2.2"
|
||||
sftp_executable:
|
||||
default: sftp
|
||||
description:
|
||||
- This defines the location of the sftp binary. It defaults to `sftp` which will use the first binary available in $PATH.
|
||||
env: [{name: ANSIBLE_SFTP_EXECUTABLE}]
|
||||
ini:
|
||||
- {key: sftp_executable, section: ssh_connection}
|
||||
version_added: "2.6"
|
||||
scp_executable:
|
||||
default: scp
|
||||
description:
|
||||
- This defines the location of the scp binary. It defaults to `scp` which will use the first binary available in $PATH.
|
||||
env: [{name: ANSIBLE_SCP_EXECUTABLE}]
|
||||
ini:
|
||||
- {key: scp_executable, section: ssh_connection}
|
||||
version_added: "2.6"
|
||||
scp_extra_args:
|
||||
description: Extra exclusive to the 'scp' CLI
|
||||
vars:
|
||||
|
@ -913,15 +928,16 @@ class Connection(ConnectionBase):
|
|||
for method in methods:
|
||||
returncode = stdout = stderr = None
|
||||
if method == 'sftp':
|
||||
cmd = self._build_command('sftp', to_bytes(host))
|
||||
cmd = self._build_command(self.get_option('sftp_executable'), to_bytes(host))
|
||||
in_data = u"{0} {1} {2}\n".format(sftp_action, shlex_quote(in_path), shlex_quote(out_path))
|
||||
in_data = to_bytes(in_data, nonstring='passthru')
|
||||
(returncode, stdout, stderr) = self._bare_run(cmd, in_data, checkrc=False)
|
||||
elif method == 'scp':
|
||||
scp = self.get_option('scp_executable')
|
||||
if sftp_action == 'get':
|
||||
cmd = self._build_command('scp', u'{0}:{1}'.format(host, shlex_quote(in_path)), out_path)
|
||||
cmd = self._build_command(scp, u'{0}:{1}'.format(host, shlex_quote(in_path)), out_path)
|
||||
else:
|
||||
cmd = self._build_command('scp', in_path, u'{0}:{1}'.format(host, shlex_quote(out_path)))
|
||||
cmd = self._build_command(scp, in_path, u'{0}:{1}'.format(host, shlex_quote(out_path)))
|
||||
in_data = None
|
||||
(returncode, stdout, stderr) = self._bare_run(cmd, in_data, checkrc=False)
|
||||
elif method == 'piped':
|
||||
|
|
|
@ -33,6 +33,7 @@ from ansible.module_utils.six.moves import shlex_quote
|
|||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible.plugins.connection import ssh
|
||||
from ansible.plugins.loader import connection_loader
|
||||
|
||||
|
||||
class TestConnectionBaseClass(unittest.TestCase):
|
||||
|
@ -68,13 +69,13 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
def test_plugins_connection_ssh__build_command(self):
|
||||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn._build_command('ssh')
|
||||
|
||||
def test_plugins_connection_ssh_exec_command(self):
|
||||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
|
||||
conn._build_command = MagicMock()
|
||||
conn._build_command.return_value = 'ssh something something'
|
||||
|
@ -90,7 +91,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
|
||||
conn.check_password_prompt = MagicMock()
|
||||
conn.check_become_success = MagicMock()
|
||||
|
@ -198,7 +199,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
def test_plugins_connection_ssh_put_file(self, mock_ospe, mock_sleep):
|
||||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn._build_command = MagicMock()
|
||||
conn._bare_run = MagicMock()
|
||||
|
||||
|
@ -255,9 +256,10 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
def test_plugins_connection_ssh_fetch_file(self, mock_sleep):
|
||||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn._build_command = MagicMock()
|
||||
conn._bare_run = MagicMock()
|
||||
conn._load_name = 'ssh'
|
||||
|
||||
conn._build_command.return_value = 'some command to run'
|
||||
conn._bare_run.return_value = (0, '', '')
|
||||
|
@ -269,6 +271,7 @@ class TestConnectionBaseClass(unittest.TestCase):
|
|||
# Test when SFTP works
|
||||
C.DEFAULT_SCP_IF_SSH = 'smart'
|
||||
expected_in_data = b' '.join((b'get', to_bytes(shlex_quote('/path/to/in/file')), to_bytes(shlex_quote('/path/to/dest/file')))) + b'\n'
|
||||
conn.set_options({})
|
||||
conn.fetch_file('/path/to/in/file', '/path/to/dest/file')
|
||||
conn._bare_run.assert_called_with('some command to run', expected_in_data, checkrc=False)
|
||||
|
||||
|
@ -327,10 +330,11 @@ def mock_run_env(request, mocker):
|
|||
pc = PlayContext()
|
||||
new_stdin = StringIO()
|
||||
|
||||
conn = ssh.Connection(pc, new_stdin)
|
||||
conn = connection_loader.get('ssh', pc, new_stdin)
|
||||
conn._send_initial_data = MagicMock()
|
||||
conn._examine_output = MagicMock()
|
||||
conn._terminate_process = MagicMock()
|
||||
conn._load_name = 'ssh'
|
||||
conn.sshpass_pipe = [MagicMock(), MagicMock()]
|
||||
|
||||
request.cls.pc = pc
|
||||
|
@ -469,27 +473,6 @@ class TestSSHConnectionRun(object):
|
|||
assert self.conn._send_initial_data.call_count == 1
|
||||
assert self.conn._send_initial_data.call_args[0][1] == 'this is input data'
|
||||
|
||||
def test_pasword_without_data(self):
|
||||
# simulate no data input
|
||||
self.mock_openpty.return_value = (98, 99)
|
||||
self.mock_popen_res.stdout.read.side_effect = [b"some data", b"", b""]
|
||||
self.mock_popen_res.stderr.read.side_effect = [b""]
|
||||
self.mock_selector.select.side_effect = [
|
||||
[(SelectorKey(self.mock_popen_res.stdout, 1001, [EVENT_READ], None), EVENT_READ)],
|
||||
[(SelectorKey(self.mock_popen_res.stdout, 1001, [EVENT_READ], None), EVENT_READ)],
|
||||
[(SelectorKey(self.mock_popen_res.stderr, 1002, [EVENT_READ], None), EVENT_READ)],
|
||||
[(SelectorKey(self.mock_popen_res.stdout, 1001, [EVENT_READ], None), EVENT_READ)],
|
||||
[]]
|
||||
self.mock_selector.get_map.side_effect = lambda: True
|
||||
|
||||
return_code, b_stdout, b_stderr = self.conn._run("ssh", "")
|
||||
assert return_code == 0
|
||||
assert b_stdout == b'some data'
|
||||
assert b_stderr == b''
|
||||
assert self.mock_selector.register.called is True
|
||||
assert self.mock_selector.register.call_count == 2
|
||||
assert self.conn._send_initial_data.called is False
|
||||
|
||||
def test_pasword_without_data(self):
|
||||
# simulate no data input but Popen using new pty's fails
|
||||
self.mock_popen.return_value = None
|
||||
|
|
Loading…
Reference in a new issue