Skip to content

Commit 38d14ee

Browse files
authored
Added fully qualified command on host output objects and tests (#411)
* Added fully qualified command on host output objects and tests * Updated changelog
1 parent 8fa5a91 commit 38d14ee

File tree

4 files changed

+63
-6
lines changed

4 files changed

+63
-6
lines changed

Changelog.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Change Log
22
==========
33

4+
2.16.0
5+
+++++++
6+
7+
Changes
8+
-------
9+
10+
* Added ``HostOutput.fully_qualified_command`` for a bytes-string of the fully qualified command executed on that host
11+
after any and all host argument subtitutions, user switching, sudo, shell switching, encoding via specified encoding
12+
et al. Populated on calls to ``run_command`` only.
13+
14+
415
2.15.0
516
++++++
617

ci/integration_tests/libssh2_clients/test_parallel_client.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,35 @@ def test_per_host_dict_args(self):
10701070
self.assertRaises(HostArgumentException, client.run_command,
10711071
cmd, host_args=[host_args[0]])
10721072

1073+
def test_per_host_args_fully_qualified_command_in_outputself(self):
1074+
host2, host3 = '127.0.0.2', '127.0.0.3'
1075+
server2 = OpenSSHServer(host2, port=self.port)
1076+
server3 = OpenSSHServer(host3, port=self.port)
1077+
servers = [server2, server3]
1078+
hosts = [self.host, host2, host3]
1079+
client = ParallelSSHClient(hosts, port=self.port,
1080+
pkey=self.user_key,
1081+
num_retries=2,
1082+
retry_delay=.2,
1083+
)
1084+
host_args = (('arg1', 'arg2'), ('arg3', 'arg4'), ('arg5', 'arg6'),)
1085+
cmd = 'echo %s %s'
1086+
my_encoding = 'utf-8'
1087+
for server in servers:
1088+
server.start_server()
1089+
try:
1090+
output = client.run_command(cmd, host_args=host_args, encoding=my_encoding)
1091+
client.join()
1092+
for i, host_output in enumerate(output):
1093+
qualified_cmd = cmd % host_args[i]
1094+
self.assertEqual(host_output.fully_qualified_command, qualified_cmd.encode(my_encoding))
1095+
self.assertEqual(host_output.fully_qualified_command.decode(host_output.encoding), qualified_cmd)
1096+
output_str = str(host_output)
1097+
self.assertTrue(len(output_str) > 0)
1098+
finally:
1099+
for server in servers:
1100+
server.stop()
1101+
10731102
def test_per_host_dict_args_invalid(self):
10741103
cmd = 'echo %(host_arg1)s %(host_arg2)s'
10751104
# Invalid number of host args

pssh/clients/base/single.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ def __init__(self, channel, client, encoding='utf-8', read_timeout=None):
158158
self._client._shell(self._chan)
159159
self._encoding = encoding
160160
self.output = self._client._make_host_output(
161-
self._chan, encoding=encoding, read_timeout=read_timeout)
161+
self._chan, encoding=encoding, read_timeout=read_timeout,
162+
fully_qualified_command=None,
163+
)
162164

163165
@property
164166
def stdout(self):
@@ -471,7 +473,7 @@ def _open_session(self):
471473
def open_session(self):
472474
raise NotImplementedError
473475

474-
def _make_host_output(self, channel, encoding, read_timeout):
476+
def _make_host_output(self, channel, encoding, read_timeout, fully_qualified_command=None):
475477
_stdout_buffer = ConcurrentRWBuffer()
476478
_stderr_buffer = ConcurrentRWBuffer()
477479
_stdout_reader, _stderr_reader = self._make_output_readers(
@@ -484,7 +486,7 @@ def _make_host_output(self, channel, encoding, read_timeout):
484486
host_out = HostOutput(
485487
host=self.host, alias=self.alias, channel=channel, stdin=Stdin(channel, self),
486488
client=self, encoding=encoding, read_timeout=read_timeout,
487-
buffers=_buffers,
489+
buffers=_buffers, fully_qualified_command=fully_qualified_command,
488490
)
489491
return host_out
490492

@@ -635,7 +637,7 @@ def run_command(self, command, sudo=False, user=None,
635637
with GTimeout(seconds=self.timeout):
636638
channel = self._execute(_command, use_pty=use_pty)
637639
_timeout = read_timeout if read_timeout else timeout
638-
host_out = self._make_host_output(channel, encoding, _timeout)
640+
host_out = self._make_host_output(channel, encoding, _timeout, fully_qualified_command=_command)
639641
return host_out
640642

641643
def _make_sftp(self):

pssh/output.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,14 @@ class HostOutput(object):
5757
__slots__ = ('host', 'channel', 'stdin',
5858
'client', 'alias', 'exception',
5959
'encoding', 'read_timeout', 'buffers',
60+
'fully_qualified_command',
6061
)
6162

6263
def __init__(self, host, channel, stdin,
6364
client, alias=None, exception=None, encoding='utf-8', read_timeout=None,
64-
buffers=None):
65+
buffers=None,
66+
fully_qualified_command=None,
67+
):
6568
"""
6669
:param host: Host name output is for
6770
:type host: str
@@ -79,6 +82,15 @@ def __init__(self, host, channel, stdin,
7982
:type read_timeout: float
8083
:param buffers: Host buffer data.
8184
:type buffers: :py:class:`HostOutputBuffers`
85+
:param fully_qualified_command: The fully qualified command after any per-host argument substitution and
86+
including command string substitution required for executing via sudo or user-switching via 'su -c', using
87+
any specified shell on `run_command` *and* conversion to bytes via provided encoding.
88+
The fully_qualified_command is therefor a bytes object that can be saved or otherwise used anywhere bytes can
89+
be used without conversion.
90+
Use `fully_qualified_command.decode(encoding)` to decode with the encoding used for the equivalent host
91+
output object.
92+
Always `None` on `HostOutput` from interactive shells.
93+
:type fully_qualified_command: bytes
8294
"""
8395
self.host = host
8496
self.channel = channel
@@ -89,6 +101,7 @@ def __init__(self, host, channel, stdin,
89101
self.encoding = encoding
90102
self.read_timeout = read_timeout
91103
self.buffers = buffers
104+
self.fully_qualified_command = fully_qualified_command
92105

93106
@property
94107
def stdout(self):
@@ -125,10 +138,12 @@ def __repr__(self):
125138
"\tchannel={channel}{linesep}" \
126139
"\texception={exception}{linesep}" \
127140
"\tencoding={encoding}{linesep}" \
128-
"\tread_timeout={read_timeout}".format(
141+
"\tread_timeout={read_timeout}{linesep}" \
142+
"\tfully_qualified_command={fully_qualified_command}".format(
129143
host=self.host, alias=self.alias, channel=self.channel,
130144
exception=self.exception, linesep=linesep,
131145
exit_code=self.exit_code, encoding=self.encoding, read_timeout=self.read_timeout,
146+
fully_qualified_command=self.fully_qualified_command,
132147
)
133148

134149
def __str__(self):

0 commit comments

Comments
 (0)