Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Change Log
==========

2.16.0
+++++++

Changes
-------

* Added ``HostOutput.fully_qualified_command`` for a bytes-string of the fully qualified command executed on that host
after any and all host argument subtitutions, user switching, sudo, shell switching, encoding via specified encoding
et al. Populated on calls to ``run_command`` only.


2.15.0
++++++

Expand Down
29 changes: 29 additions & 0 deletions ci/integration_tests/libssh2_clients/test_parallel_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,35 @@ def test_per_host_dict_args(self):
self.assertRaises(HostArgumentException, client.run_command,
cmd, host_args=[host_args[0]])

def test_per_host_args_fully_qualified_command_in_outputself(self):
host2, host3 = '127.0.0.2', '127.0.0.3'
server2 = OpenSSHServer(host2, port=self.port)
server3 = OpenSSHServer(host3, port=self.port)
servers = [server2, server3]
hosts = [self.host, host2, host3]
client = ParallelSSHClient(hosts, port=self.port,
pkey=self.user_key,
num_retries=2,
retry_delay=.2,
)
host_args = (('arg1', 'arg2'), ('arg3', 'arg4'), ('arg5', 'arg6'),)
cmd = 'echo %s %s'
my_encoding = 'utf-8'
for server in servers:
server.start_server()
try:
output = client.run_command(cmd, host_args=host_args, encoding=my_encoding)
client.join()
for i, host_output in enumerate(output):
qualified_cmd = cmd % host_args[i]
self.assertEqual(host_output.fully_qualified_command, qualified_cmd.encode(my_encoding))
self.assertEqual(host_output.fully_qualified_command.decode(host_output.encoding), qualified_cmd)
output_str = str(host_output)
self.assertTrue(len(output_str) > 0)
finally:
for server in servers:
server.stop()

def test_per_host_dict_args_invalid(self):
cmd = 'echo %(host_arg1)s %(host_arg2)s'
# Invalid number of host args
Expand Down
10 changes: 6 additions & 4 deletions pssh/clients/base/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,9 @@ def __init__(self, channel, client, encoding='utf-8', read_timeout=None):
self._client._shell(self._chan)
self._encoding = encoding
self.output = self._client._make_host_output(
self._chan, encoding=encoding, read_timeout=read_timeout)
self._chan, encoding=encoding, read_timeout=read_timeout,
fully_qualified_command=None,
)

@property
def stdout(self):
Expand Down Expand Up @@ -471,7 +473,7 @@ def _open_session(self):
def open_session(self):
raise NotImplementedError

def _make_host_output(self, channel, encoding, read_timeout):
def _make_host_output(self, channel, encoding, read_timeout, fully_qualified_command=None):
_stdout_buffer = ConcurrentRWBuffer()
_stderr_buffer = ConcurrentRWBuffer()
_stdout_reader, _stderr_reader = self._make_output_readers(
Expand All @@ -484,7 +486,7 @@ def _make_host_output(self, channel, encoding, read_timeout):
host_out = HostOutput(
host=self.host, alias=self.alias, channel=channel, stdin=Stdin(channel, self),
client=self, encoding=encoding, read_timeout=read_timeout,
buffers=_buffers,
buffers=_buffers, fully_qualified_command=fully_qualified_command,
)
return host_out

Expand Down Expand Up @@ -635,7 +637,7 @@ def run_command(self, command, sudo=False, user=None,
with GTimeout(seconds=self.timeout):
channel = self._execute(_command, use_pty=use_pty)
_timeout = read_timeout if read_timeout else timeout
host_out = self._make_host_output(channel, encoding, _timeout)
host_out = self._make_host_output(channel, encoding, _timeout, fully_qualified_command=_command)
return host_out

def _make_sftp(self):
Expand Down
19 changes: 17 additions & 2 deletions pssh/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,14 @@ class HostOutput(object):
__slots__ = ('host', 'channel', 'stdin',
'client', 'alias', 'exception',
'encoding', 'read_timeout', 'buffers',
'fully_qualified_command',
)

def __init__(self, host, channel, stdin,
client, alias=None, exception=None, encoding='utf-8', read_timeout=None,
buffers=None):
buffers=None,
fully_qualified_command=None,
):
"""
:param host: Host name output is for
:type host: str
Expand All @@ -79,6 +82,15 @@ def __init__(self, host, channel, stdin,
:type read_timeout: float
:param buffers: Host buffer data.
:type buffers: :py:class:`HostOutputBuffers`
:param fully_qualified_command: The fully qualified command after any per-host argument substitution and
including command string substitution required for executing via sudo or user-switching via 'su -c', using
any specified shell on `run_command` *and* conversion to bytes via provided encoding.
The fully_qualified_command is therefor a bytes object that can be saved or otherwise used anywhere bytes can
be used without conversion.
Use `fully_qualified_command.decode(encoding)` to decode with the encoding used for the equivalent host
output object.
Always `None` on `HostOutput` from interactive shells.
:type fully_qualified_command: bytes
"""
self.host = host
self.channel = channel
Expand All @@ -89,6 +101,7 @@ def __init__(self, host, channel, stdin,
self.encoding = encoding
self.read_timeout = read_timeout
self.buffers = buffers
self.fully_qualified_command = fully_qualified_command

@property
def stdout(self):
Expand Down Expand Up @@ -125,10 +138,12 @@ def __repr__(self):
"\tchannel={channel}{linesep}" \
"\texception={exception}{linesep}" \
"\tencoding={encoding}{linesep}" \
"\tread_timeout={read_timeout}".format(
"\tread_timeout={read_timeout}{linesep}" \
"\tfully_qualified_command={fully_qualified_command}".format(
host=self.host, alias=self.alias, channel=self.channel,
exception=self.exception, linesep=linesep,
exit_code=self.exit_code, encoding=self.encoding, read_timeout=self.read_timeout,
fully_qualified_command=self.fully_qualified_command,
)

def __str__(self):
Expand Down