diff options
author | Ross Brattain <ross.b.brattain@intel.com> | 2016-12-20 21:40:52 -0800 |
---|---|---|
committer | Ross Brattain <ross.b.brattain@intel.com> | 2016-12-20 21:47:37 -0800 |
commit | 48a7b4fa8a9cfa2db8c002ffb68c46345551ee2a (patch) | |
tree | 717c748b93e58c18aa7e790441727171cd50bfe5 | |
parent | cd34d540ce1d5da5ba5df0a2d169013b5b222418 (diff) |
ssh: don't quote ~ in remotepaths
~ is not expanded in double quotes, so we have a dilemma.
We need to quote in order to preserve filenames with spaces,
but we have to make sure we don't quote the ~ so it can be expanded.
To resolve this we use a regex to search for tidle-prefixes
and excluded them from quotes.
Added unittests for the cases:
path with tilde
path with space
path with tilde and space
see bash man page for details of tidle expansion
Tilde Expansion
If a word begins with an unquoted tilde character (`~'), all of the
characters preceding the first unquoted slash (or all characters, if there is
no unquoted slash) are considered a tilde-prefix. If none of the characters in
the tilde-prefix are quoted, the characters in the tilde-prefix following the
tilde are treated as a possible login name. If this login name is the null
string, the tilde is replaced with the value of the shell parameter HOME. If
HOME is unset, the home directory of the user executing the shell is
substituted instead. Otherwise, the tilde-prefix is replaced with the
home directory associated with the specified login name.
JIRA: YARDSTICK-501
Change-Id: I324be20aba0dbd50434fbd8081685c598ebd8a84
Signed-off-by: Ross Brattain <ross.b.brattain@intel.com>
-rw-r--r-- | tests/unit/test_ssh.py | 36 | ||||
-rw-r--r-- | yardstick/ssh.py | 16 |
2 files changed, 41 insertions, 11 deletions
diff --git a/tests/unit/test_ssh.py b/tests/unit/test_ssh.py index 8b828ed7c..045ac0f1b 100644 --- a/tests/unit/test_ssh.py +++ b/tests/unit/test_ssh.py @@ -310,12 +310,38 @@ class SSHRunTestCase(unittest.TestCase): @mock.patch("yardstick.ssh.open", create=True) def test__put_file_shell(self, mock_open): - self.test_client.run = mock.Mock() - self.test_client._put_file_shell("localfile", "remotefile", 0o42) + with mock.patch.object(self.test_client, "run") as run_mock: + self.test_client._put_file_shell("localfile", "remotefile", 0o42) + run_mock.assert_called_once_with( + 'cat > "remotefile"&& chmod -- 042 "remotefile"', + stdin=mock_open.return_value.__enter__.return_value) - self.test_client.run.assert_called_once_with( - 'cat > remotefile && chmod -- 042 remotefile', - stdin=mock_open.return_value.__enter__.return_value) + @mock.patch("yardstick.ssh.open", create=True) + def test__put_file_shell_space(self, mock_open): + with mock.patch.object(self.test_client, "run") as run_mock: + self.test_client._put_file_shell("localfile", + "filename with space", 0o42) + run_mock.assert_called_once_with( + 'cat > "filename with space"&& chmod -- 042 "filename with ' + 'space"', + stdin=mock_open.return_value.__enter__.return_value) + + @mock.patch("yardstick.ssh.open", create=True) + def test__put_file_shell_tilde(self, mock_open): + with mock.patch.object(self.test_client, "run") as run_mock: + self.test_client._put_file_shell("localfile", "~/remotefile", 0o42) + run_mock.assert_called_once_with( + 'cat > ~/"remotefile"&& chmod -- 042 ~/"remotefile"', + stdin=mock_open.return_value.__enter__.return_value) + + @mock.patch("yardstick.ssh.open", create=True) + def test__put_file_shell_tilde_spaces(self, mock_open): + with mock.patch.object(self.test_client, "run") as run_mock: + self.test_client._put_file_shell("localfile", "~/file with space", + 0o42) + run_mock.assert_called_once_with( + 'cat > ~/"file with space"&& chmod -- 042 ~/"file with space"', + stdin=mock_open.return_value.__enter__.return_value) @mock.patch("yardstick.ssh.os.stat") def test__put_file_sftp(self, mock_stat): diff --git a/yardstick/ssh.py b/yardstick/ssh.py index 3081001b6..927ca94db 100644 --- a/yardstick/ssh.py +++ b/yardstick/ssh.py @@ -66,6 +66,7 @@ import os import select import socket import time +import re import logging import paramiko @@ -252,7 +253,7 @@ class SSH(object): raise SSHError("Socket error.") exit_status = session.recv_exit_status() - if 0 != exit_status and raise_on_error: + if exit_status != 0 and raise_on_error: fmt = "Command '%(cmd)s' failed with exit_status %(status)d." details = fmt % {"cmd": cmd, "status": exit_status} if stderr_data: @@ -311,17 +312,21 @@ class SSH(object): mode = 0o777 & os.stat(localpath).st_mode sftp.chmod(remotepath, mode) + TILDE_EXPANSIONS_RE = re.compile("(^~[^/]*/)?(.*)") + def _put_file_shell(self, localpath, remotepath, mode=None): # quote to stop wordpslit - cmd = ['cat > %s' % remotepath] + tilde, remotepath = self.TILDE_EXPANSIONS_RE.match(remotepath).groups() + if not tilde: + tilde = '' + cmd = ['cat > %s"%s"' % (tilde, remotepath)] if mode is not None: # use -- so no options - cmd.append('chmod -- 0%o %s' % (mode, remotepath)) + cmd.append('chmod -- 0%o %s"%s"' % (mode, tilde, remotepath)) with open(localpath, "rb") as localfile: # only chmod on successful cat - cmd = " && ".join(cmd) - self.run(cmd, stdin=localfile) + self.run("&& ".join(cmd), stdin=localfile) def put_file(self, localpath, remotepath, mode=None): """Copy specified local file to the server. @@ -330,7 +335,6 @@ class SSH(object): :param remotepath: Remote filename. :param mode: Permissions to set after upload """ - import socket try: self._put_file_sftp(localpath, remotepath, mode=mode) except (paramiko.SSHException, socket.error): |