parallel-ssh 2.11.1__tar.gz → 2.13.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. parallel_ssh-2.13.0/MANIFEST.in +14 -0
  2. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/PKG-INFO +9 -12
  3. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/README.rst +3 -6
  4. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/PKG-INFO +9 -12
  5. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/requires.txt +1 -1
  6. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/__init__.py +3 -3
  7. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/_version.py +4 -4
  8. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/__init__.py +2 -3
  9. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/parallel.py +38 -27
  10. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/single.py +22 -14
  11. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/parallel.py +11 -21
  12. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/single.py +56 -32
  13. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/tunnel.py +2 -7
  14. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/reader.py +24 -9
  15. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/parallel.py +1 -0
  16. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/single.py +26 -28
  17. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/config.py +7 -2
  18. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/output.py +10 -6
  19. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/setup.cfg +1 -1
  20. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/setup.py +3 -3
  21. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/versioneer.py +776 -346
  22. parallel-ssh-2.11.1/MANIFEST.in +0 -6
  23. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/COPYING +0 -0
  24. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/COPYING.LESSER +0 -0
  25. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/LICENSE +0 -0
  26. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/SOURCES.txt +0 -0
  27. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/dependency_links.txt +0 -0
  28. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/top_level.txt +0 -0
  29. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/__init__.py +0 -0
  30. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/common.py +0 -0
  31. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/__init__.py +0 -0
  32. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/__init__.py +0 -0
  33. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/constants.py +0 -0
  34. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/exceptions.py +0 -0
  35. {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/utils.py +0 -0
@@ -0,0 +1,14 @@
1
+ include versioneer.py
2
+ include pssh/_version.py
3
+ include LICENSE
4
+ include COPYING
5
+ include COPYING.LESSER
6
+ exclude .codecov.yml
7
+ exclude .coveragerc
8
+ exclude .git*
9
+ exclude .pre-commit*
10
+ exclude .readthedocs.yml
11
+ recursive-exclude tests *
12
+ recursive-exclude ci *
13
+ recursive-exclude .circleci *
14
+ recursive-exclude .github *
@@ -1,23 +1,22 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parallel-ssh
3
- Version: 2.11.1
3
+ Version: 2.13.0
4
4
  Summary: Asynchronous parallel SSH library
5
5
  Home-page: https://github.com/ParallelSSH/parallel-ssh
6
6
  Author: Panos Kittenis
7
7
  Author-email: zuboci@yandex.com
8
8
  License: LGPLv2.1
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: Operating System :: OS Independent
14
13
  Classifier: Programming Language :: Python
15
14
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.7
18
15
  Classifier: Programming Language :: Python :: 3.8
19
16
  Classifier: Programming Language :: Python :: 3.9
20
17
  Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
21
20
  Classifier: Topic :: System :: Networking
22
21
  Classifier: Topic :: Software Development :: Libraries
23
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -28,6 +27,9 @@ Classifier: Operating System :: MacOS :: MacOS X
28
27
  License-File: LICENSE
29
28
  License-File: COPYING
30
29
  License-File: COPYING.LESSER
30
+ Requires-Dist: gevent
31
+ Requires-Dist: ssh2-python
32
+ Requires-Dist: ssh-python
31
33
 
32
34
  ============
33
35
  parallel-ssh
@@ -56,6 +58,7 @@ Native code based clients with extremely high performance, making use of C libra
56
58
  :alt: Latest documentation
57
59
 
58
60
  .. _`read the docs`: https://parallel-ssh.readthedocs.org/en/latest/
61
+ .. _`SFTP and SCP documentation`: https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp
59
62
 
60
63
  ************
61
64
  Installation
@@ -270,7 +273,7 @@ To copy a local file to remote hosts in parallel with SCP:
270
273
  cmds = client.scp_send('../test', 'test_dir/test')
271
274
  joinall(cmds, raise_error=True)
272
275
 
273
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
276
+ See `SFTP and SCP documentation`_ for more examples.
274
277
 
275
278
 
276
279
  *****
@@ -306,10 +309,4 @@ In addition, per-host configurable file name functionality is provided for both
306
309
 
307
310
  Directory recursion is supported in both cases via the ``recurse`` parameter - defaults to off.
308
311
 
309
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
310
-
311
-
312
- .. image:: https://ga-beacon.appspot.com/UA-9132694-7/parallel-ssh/README.rst?pixel
313
- :target: https://github.com/igrigorik/ga-beacon
314
-
315
-
312
+ See `SFTP and SCP documentation`_ for more examples.
@@ -25,6 +25,7 @@ Native code based clients with extremely high performance, making use of C libra
25
25
  :alt: Latest documentation
26
26
 
27
27
  .. _`read the docs`: https://parallel-ssh.readthedocs.org/en/latest/
28
+ .. _`SFTP and SCP documentation`: https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp
28
29
 
29
30
  ************
30
31
  Installation
@@ -239,7 +240,7 @@ To copy a local file to remote hosts in parallel with SCP:
239
240
  cmds = client.scp_send('../test', 'test_dir/test')
240
241
  joinall(cmds, raise_error=True)
241
242
 
242
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
243
+ See `SFTP and SCP documentation`_ for more examples.
243
244
 
244
245
 
245
246
  *****
@@ -275,8 +276,4 @@ In addition, per-host configurable file name functionality is provided for both
275
276
 
276
277
  Directory recursion is supported in both cases via the ``recurse`` parameter - defaults to off.
277
278
 
278
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
279
-
280
-
281
- .. image:: https://ga-beacon.appspot.com/UA-9132694-7/parallel-ssh/README.rst?pixel
282
- :target: https://github.com/igrigorik/ga-beacon
279
+ See `SFTP and SCP documentation`_ for more examples.
@@ -1,23 +1,22 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: parallel-ssh
3
- Version: 2.11.1
3
+ Version: 2.13.0
4
4
  Summary: Asynchronous parallel SSH library
5
5
  Home-page: https://github.com/ParallelSSH/parallel-ssh
6
6
  Author: Panos Kittenis
7
7
  Author-email: zuboci@yandex.com
8
8
  License: LGPLv2.1
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 5 - Production/Stable
11
10
  Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: Operating System :: OS Independent
14
13
  Classifier: Programming Language :: Python
15
14
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.7
18
15
  Classifier: Programming Language :: Python :: 3.8
19
16
  Classifier: Programming Language :: Python :: 3.9
20
17
  Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
21
20
  Classifier: Topic :: System :: Networking
22
21
  Classifier: Topic :: Software Development :: Libraries
23
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -28,6 +27,9 @@ Classifier: Operating System :: MacOS :: MacOS X
28
27
  License-File: LICENSE
29
28
  License-File: COPYING
30
29
  License-File: COPYING.LESSER
30
+ Requires-Dist: gevent
31
+ Requires-Dist: ssh2-python
32
+ Requires-Dist: ssh-python
31
33
 
32
34
  ============
33
35
  parallel-ssh
@@ -56,6 +58,7 @@ Native code based clients with extremely high performance, making use of C libra
56
58
  :alt: Latest documentation
57
59
 
58
60
  .. _`read the docs`: https://parallel-ssh.readthedocs.org/en/latest/
61
+ .. _`SFTP and SCP documentation`: https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp
59
62
 
60
63
  ************
61
64
  Installation
@@ -270,7 +273,7 @@ To copy a local file to remote hosts in parallel with SCP:
270
273
  cmds = client.scp_send('../test', 'test_dir/test')
271
274
  joinall(cmds, raise_error=True)
272
275
 
273
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
276
+ See `SFTP and SCP documentation`_ for more examples.
274
277
 
275
278
 
276
279
  *****
@@ -306,10 +309,4 @@ In addition, per-host configurable file name functionality is provided for both
306
309
 
307
310
  Directory recursion is supported in both cases via the ``recurse`` parameter - defaults to off.
308
311
 
309
- See `SFTP and SCP documentation <https://parallel-ssh.readthedocs.io/en/latest/advanced.html#sftp-scp>`_ for more examples.
310
-
311
-
312
- .. image:: https://ga-beacon.appspot.com/UA-9132694-7/parallel-ssh/README.rst?pixel
313
- :target: https://github.com/igrigorik/ga-beacon
314
-
315
-
312
+ See `SFTP and SCP documentation`_ for more examples.
@@ -1,3 +1,3 @@
1
- gevent>=1.3.0
1
+ gevent
2
2
  ssh2-python
3
3
  ssh-python
@@ -29,9 +29,9 @@ for class documentation.
29
29
 
30
30
 
31
31
  from logging import getLogger, NullHandler
32
- from ._version import get_versions
33
- __version__ = get_versions()['version']
34
- del get_versions
32
+ from . import _version
33
+ __version__ = _version.get_versions()['version']
34
+ del _version
35
35
 
36
36
  host_logger = getLogger('pssh.host_logger')
37
37
  logger = getLogger('pssh')
@@ -1,5 +1,5 @@
1
1
 
2
- # This file was generated by 'versioneer.py' (0.18) from
2
+ # This file was generated by 'versioneer.py' (0.29) from
3
3
  # revision-control system data, or from the parent directory name of an
4
4
  # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
5
  # of this file.
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2022-07-31T18:09:28+0100",
11
+ "date": "2025-01-13T03:04:42+0000",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "29d94979184f5842155b2250ad87cbef0f9df138",
15
- "version": "2.11.1"
14
+ "full-revisionid": "f1d46fcf86c331b21dc199f5129f59da3bc1284e",
15
+ "version": "2.13.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -1,6 +1,6 @@
1
1
  # This file is part of parallel-ssh.
2
2
  #
3
- # Copyright (C) 2014-2022 Panos Kittenis and contributors.
3
+ # Copyright (C) 2014-2025 Panos Kittenis and contributors.
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -16,5 +16,4 @@
16
16
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
  # flake8: noqa: F401
19
- from .native.parallel import ParallelSSHClient
20
- from .native.single import SSHClient
19
+ from .native import ParallelSSHClient, SSHClient
@@ -23,7 +23,7 @@ import gevent.pool
23
23
  from gevent import joinall, spawn, Timeout as GTimeout
24
24
  from gevent.hub import Hub
25
25
 
26
- from ..common import _validate_pkey_path
26
+ from ..common import _validate_pkey_path, _validate_pkey
27
27
  from ...config import HostConfig
28
28
  from ...constants import DEFAULT_RETRIES, RETRY_DELAY
29
29
  from ...exceptions import HostArgumentError, Timeout, ShellError, HostConfigError
@@ -39,7 +39,7 @@ class BaseParallelSSHClient(object):
39
39
  def __init__(self, hosts, user=None, password=None, port=None, pkey=None,
40
40
  allow_agent=True,
41
41
  num_retries=DEFAULT_RETRIES,
42
- timeout=120, pool_size=10,
42
+ timeout=120, pool_size=100,
43
43
  host_config=None, retry_delay=RETRY_DELAY,
44
44
  identity_auth=True,
45
45
  ipv6_only=False,
@@ -64,7 +64,8 @@ class BaseParallelSSHClient(object):
64
64
  self.user = user
65
65
  self.password = password
66
66
  self.port = port
67
- self.pkey = pkey
67
+ self.pkey = _validate_pkey(pkey)
68
+ self.__pkey_data = self._load_pkey_data(pkey) if pkey is not None else None
68
69
  self.num_retries = num_retries
69
70
  self.timeout = timeout
70
71
  self._host_clients = {}
@@ -113,9 +114,26 @@ class BaseParallelSSHClient(object):
113
114
  self._host_clients.pop((i, host), None)
114
115
  self._hosts = _hosts
115
116
 
117
+ def __del__(self):
118
+ self.disconnect()
119
+
120
+ def disconnect(self):
121
+ """Disconnect all clients."""
122
+ if not hasattr(self, '_host_clients'):
123
+ return
124
+ for s_client in self._host_clients.values():
125
+ try:
126
+ s_client.disconnect()
127
+ except Exception as ex:
128
+ logger.debug("Client disconnect failed with %s", ex)
129
+ pass
130
+
116
131
  def _check_host_config(self):
117
132
  if self.host_config is None:
118
133
  return
134
+ if not isinstance(self.host_config, list):
135
+ raise HostConfigError("Host configuration of type %s is invalid - valid types are List[HostConfig]",
136
+ type(self.host_config))
119
137
  host_len = len(self.hosts)
120
138
  if host_len != len(self.host_config):
121
139
  raise ValueError(
@@ -231,6 +249,7 @@ class BaseParallelSSHClient(object):
231
249
 
232
250
  def _get_output_from_greenlet(self, cmd_i, cmd, raise_error=False):
233
251
  host = self.hosts[cmd_i]
252
+ alias = self._get_host_config(cmd_i).alias
234
253
  try:
235
254
  host_out = cmd.get()
236
255
  return host_out
@@ -239,8 +258,7 @@ class BaseParallelSSHClient(object):
239
258
  ex = Timeout()
240
259
  if raise_error:
241
260
  raise ex
242
- return HostOutput(host, None, None, None,
243
- exception=ex)
261
+ return HostOutput(host, None, None, None, exception=ex, alias=alias)
244
262
 
245
263
  def get_last_output(self, cmds=None):
246
264
  """Get output for last commands executed by ``run_command``.
@@ -248,7 +266,7 @@ class BaseParallelSSHClient(object):
248
266
  :param cmds: Commands to get output for. Defaults to ``client.cmds``
249
267
  :type cmds: list(:py:class:`gevent.Greenlet`)
250
268
 
251
- :rtype: dict or list
269
+ :rtype: list(:py:class:`pssh.output.HostOutput`)
252
270
  """
253
271
  cmds = self.cmds if cmds is None else cmds
254
272
  if cmds is None:
@@ -256,7 +274,7 @@ class BaseParallelSSHClient(object):
256
274
  return self._get_output_from_cmds(
257
275
  cmds, raise_error=False)
258
276
 
259
- def _get_host_config(self, host_i, host):
277
+ def _get_host_config(self, host_i):
260
278
  if self.host_config is None:
261
279
  config = HostConfig(
262
280
  user=self.user, port=self.port, password=self.password, private_key=self.pkey,
@@ -274,9 +292,6 @@ class BaseParallelSSHClient(object):
274
292
  gssapi_delegate_credentials=self.gssapi_delegate_credentials,
275
293
  )
276
294
  return config
277
- elif not isinstance(self.host_config, list):
278
- raise HostConfigError("Host configuration of type %s is invalid - valid types are list[HostConfig]",
279
- type(self.host_config))
280
295
  config = self.host_config[host_i]
281
296
  return config
282
297
 
@@ -284,7 +299,6 @@ class BaseParallelSSHClient(object):
284
299
  shell=None, use_pty=False,
285
300
  encoding='utf-8', read_timeout=None):
286
301
  """Make SSHClient if needed, run command on host"""
287
- logger.debug("_run_command with read timeout %s", read_timeout)
288
302
  try:
289
303
  _client = self._get_ssh_client(host_i, host)
290
304
  host_out = _client.run_command(
@@ -310,13 +324,13 @@ class BaseParallelSSHClient(object):
310
324
  :returns: list of greenlets to ``joinall`` with.
311
325
  :rtype: list(:py:mod:`gevent.greenlet.Greenlet`)
312
326
  """
313
- cmds = [spawn(self._get_ssh_client, i, host) for i, host in enumerate(self.hosts)]
327
+ cmds = [self.pool.spawn(self._get_ssh_client, i, host) for i, host in enumerate(self.hosts)]
314
328
  return cmds
315
329
 
316
330
  def _consume_output(self, stdout, stderr):
317
- for line in stdout:
331
+ for _ in stdout:
318
332
  pass
319
- for line in stderr:
333
+ for _ in stderr:
320
334
  pass
321
335
 
322
336
  def join(self, output=None, consume_output=False, timeout=None):
@@ -345,6 +359,9 @@ class BaseParallelSSHClient(object):
345
359
  :rtype: ``None``"""
346
360
  if output is None:
347
361
  output = self.get_last_output()
362
+ if output is None:
363
+ logger.info("No last output to join on - run_command has never been run.")
364
+ return
348
365
  elif not isinstance(output, list):
349
366
  raise ValueError("Unexpected output object type")
350
367
  cmds = [self.pool.spawn(self._join, host_out, timeout=timeout,
@@ -543,19 +560,13 @@ class BaseParallelSSHClient(object):
543
560
  return client.copy_remote_file(
544
561
  remote_file, local_file, recurse=recurse, **kwargs)
545
562
 
546
- def _handle_greenlet_exc(self, func, host, *args, **kwargs):
547
- try:
548
- return func(*args, **kwargs)
549
- except Exception as ex:
550
- raise ex
551
-
552
563
  def _get_ssh_client(self, host_i, host):
553
564
  logger.debug("Make client request for host %s, (host_i, host) in clients: %s",
554
565
  host, (host_i, host) in self._host_clients)
555
566
  _client = self._host_clients.get((host_i, host))
556
567
  if _client is not None:
557
568
  return _client
558
- cfg = self._get_host_config(host_i, host)
569
+ cfg = self._get_host_config(host_i)
559
570
  _pkey = self.pkey if cfg.private_key is None else cfg.private_key
560
571
  _pkey_data = self._load_pkey_data(_pkey)
561
572
  _client = self._make_ssh_client(host, cfg, _pkey_data)
@@ -563,12 +574,12 @@ class BaseParallelSSHClient(object):
563
574
  return _client
564
575
 
565
576
  def _load_pkey_data(self, _pkey):
566
- if isinstance(_pkey, str):
567
- _validate_pkey_path(_pkey)
568
- with open(_pkey, 'rb') as fh:
569
- _pkey_data = fh.read()
570
- return _pkey_data
571
- return _pkey
577
+ if not isinstance(_pkey, str):
578
+ return _pkey
579
+ _pkey = _validate_pkey_path(_pkey)
580
+ with open(_pkey, 'rb') as fh:
581
+ _pkey_data = fh.read()
582
+ return _pkey_data
572
583
 
573
584
  def _make_ssh_client(self, host, cfg, _pkey_data):
574
585
  raise NotImplementedError
@@ -23,19 +23,17 @@ from socket import gaierror as sock_gaierror, error as sock_error
23
23
  from gevent import sleep, socket, Timeout as GTimeout
24
24
  from gevent.hub import Hub
25
25
  from gevent.select import poll, POLLIN, POLLOUT
26
-
27
- from ssh2.utils import find_eol
28
26
  from ssh2.exceptions import AgentConnectionError, AgentListIdentitiesError, \
29
27
  AgentAuthenticationError, AgentGetIdentityError
28
+ from ssh2.utils import find_eol
30
29
 
31
30
  from ..common import _validate_pkey
32
- from ...constants import DEFAULT_RETRIES, RETRY_DELAY
33
31
  from ..reader import ConcurrentRWBuffer
32
+ from ...constants import DEFAULT_RETRIES, RETRY_DELAY
34
33
  from ...exceptions import UnknownHostError, AuthenticationError, \
35
34
  ConnectionError, Timeout, NoIPv6AddressFoundError
36
35
  from ...output import HostOutput, HostOutputBuffers, BufferData
37
36
 
38
-
39
37
  Hub.NOT_ERROR = (Exception,)
40
38
  host_logger = logging.getLogger('pssh.host_logger')
41
39
  logger = logging.getLogger(__name__)
@@ -70,6 +68,8 @@ class Stdin(object):
70
68
 
71
69
  def flush(self):
72
70
  """Flush pending data written to stdin."""
71
+ if not hasattr(self._channel, "flush"):
72
+ return
73
73
  return self._client._eagain(self._channel.flush)
74
74
 
75
75
 
@@ -159,7 +159,7 @@ class BaseSSHClient(object):
159
159
 
160
160
  def __init__(self, host,
161
161
  user=None, password=None, port=None,
162
- pkey=None,
162
+ pkey=None, alias=None,
163
163
  num_retries=DEFAULT_RETRIES,
164
164
  retry_delay=RETRY_DELAY,
165
165
  allow_agent=True, timeout=None,
@@ -171,6 +171,7 @@ class BaseSSHClient(object):
171
171
  ):
172
172
  self._auth_thread_pool = _auth_thread_pool
173
173
  self.host = host
174
+ self.alias = alias
174
175
  self.user = user if user else getuser()
175
176
  self.password = password
176
177
  self.port = port if port else 22
@@ -286,7 +287,7 @@ class BaseSSHClient(object):
286
287
  raise unknown_ex from ex
287
288
  for i, (family, _type, proto, _, sock_addr) in enumerate(addr_info):
288
289
  try:
289
- return self._connect_socket(family, _type, proto, sock_addr, host, port, retries)
290
+ return self._connect_socket(family, _type, sock_addr, host, port, retries)
290
291
  except ConnectionRefusedError as ex:
291
292
  if i+1 == len(addr_info):
292
293
  logger.error("No available addresses from %s", [addr[4] for addr in addr_info])
@@ -294,7 +295,7 @@ class BaseSSHClient(object):
294
295
  raise
295
296
  continue
296
297
 
297
- def _connect_socket(self, family, _type, proto, sock_addr, host, port, retries):
298
+ def _connect_socket(self, family, _type, sock_addr, host, port, retries):
298
299
  self.sock = socket.socket(family, _type)
299
300
  if self.timeout:
300
301
  self.sock.settimeout(self.timeout)
@@ -409,7 +410,7 @@ class BaseSSHClient(object):
409
410
  stdout=BufferData(rw_buffer=_stdout_buffer, reader=_stdout_reader),
410
411
  stderr=BufferData(rw_buffer=_stderr_buffer, reader=_stderr_reader))
411
412
  host_out = HostOutput(
412
- host=self.host, channel=channel, stdin=Stdin(channel, self),
413
+ host=self.host, alias=self.alias, channel=channel, stdin=Stdin(channel, self),
413
414
  client=self, encoding=encoding, read_timeout=read_timeout,
414
415
  buffers=_buffers,
415
416
  )
@@ -427,6 +428,8 @@ class BaseSSHClient(object):
427
428
 
428
429
  :param stderr_buffer: Buffer to read from.
429
430
  :type stderr_buffer: :py:class:`pssh.clients.reader.ConcurrentRWBuffer`
431
+ :param timeout: Timeout in seconds - defaults to no timeout.
432
+ :type timeout: int or float
430
433
  :rtype: generator
431
434
  """
432
435
  logger.debug("Reading from stderr buffer, timeout=%s", timeout)
@@ -438,6 +441,8 @@ class BaseSSHClient(object):
438
441
 
439
442
  :param stdout_buffer: Buffer to read from.
440
443
  :type stdout_buffer: :py:class:`pssh.clients.reader.ConcurrentRWBuffer`
444
+ :param timeout: Timeout in seconds - defaults to no timeout.
445
+ :type timeout: int or float
441
446
  :rtype: generator
442
447
  """
443
448
  logger.debug("Reading from stdout buffer, timeout=%s", timeout)
@@ -491,14 +496,16 @@ class BaseSSHClient(object):
491
496
  encoding='utf-8'):
492
497
  """Read from output buffers and log to ``host_logger``.
493
498
 
494
- :param output_buffer: Iterator containing buffer
499
+ :param output_buffer: Iterator containing buffer.
495
500
  :type output_buffer: iterator
496
- :param prefix: String to prefix log output to ``host_logger`` with
501
+ :param prefix: String to prefix log output to ``host_logger`` with.
497
502
  :type prefix: str
498
- :param callback: Function to call back once buffer is depleted:
503
+ :param callback: Function to call back once buffer is depleted.
499
504
  :type callback: function
500
- :param callback_args: Arguments for call back function
505
+ :param callback_args: Arguments for call back function.
501
506
  :type callback_args: tuple
507
+ :param encoding: Encoding for output.
508
+ :type encoding: str
502
509
  """
503
510
  prefix = '' if prefix is None else prefix
504
511
  for line in output_buffer:
@@ -552,7 +559,7 @@ class BaseSSHClient(object):
552
559
  host_out = self._make_host_output(channel, encoding, _timeout)
553
560
  return host_out
554
561
 
555
- def _eagain_write_errcode(self, write_func, data, eagain, timeout=None):
562
+ def _eagain_write_errcode(self, write_func, data, eagain):
556
563
  data_len = len(data)
557
564
  total_written = 0
558
565
  while total_written < data_len:
@@ -569,9 +576,10 @@ class BaseSSHClient(object):
569
576
  while ret == eagain:
570
577
  self.poll()
571
578
  ret = func(*args, **kwargs)
579
+ sleep()
572
580
  return ret
573
581
 
574
- def _eagain_write(self, write_func, data, timeout=None):
582
+ def _eagain_write(self, write_func, data):
575
583
  raise NotImplementedError
576
584
 
577
585
  def _eagain(self, func, *args, **kwargs):
@@ -127,7 +127,6 @@ class ParallelSSHClient(BaseParallelSSHClient):
127
127
  identity_auth=identity_auth,
128
128
  ipv6_only=ipv6_only,
129
129
  )
130
- self.pkey = _validate_pkey(pkey)
131
130
  self.proxy_host = proxy_host
132
131
  self.proxy_port = proxy_port
133
132
  self.proxy_pkey = _validate_pkey(proxy_pkey)
@@ -216,21 +215,11 @@ class ParallelSSHClient(BaseParallelSSHClient):
216
215
  read_timeout=read_timeout,
217
216
  )
218
217
 
219
- def __del__(self):
220
- if not hasattr(self, '_host_clients'):
221
- return
222
- for s_client in self._host_clients.values():
223
- try:
224
- s_client.disconnect()
225
- except Exception as ex:
226
- logger.debug("Client disconnect failed with %s", ex)
227
- pass
228
- del s_client
229
-
230
218
  def _make_ssh_client(self, host, cfg, _pkey_data):
231
219
  _client = SSHClient(
232
220
  host, user=cfg.user or self.user, password=cfg.password or self.password, port=cfg.port or self.port,
233
221
  pkey=_pkey_data, num_retries=cfg.num_retries or self.num_retries,
222
+ alias=cfg.alias,
234
223
  timeout=cfg.timeout or self.timeout,
235
224
  allow_agent=cfg.allow_agent or self.allow_agent, retry_delay=cfg.retry_delay or self.retry_delay,
236
225
  proxy_host=cfg.proxy_host or self.proxy_host,
@@ -370,16 +359,12 @@ class ParallelSSHClient(BaseParallelSSHClient):
370
359
  encoding=encoding)
371
360
 
372
361
  def _scp_send(self, host_i, host, local_file, remote_file, recurse=False):
373
- self._get_ssh_client(host_i, host)
374
- return self._handle_greenlet_exc(
375
- self._host_clients[(host_i, host)].scp_send, host,
376
- local_file, remote_file, recurse=recurse)
362
+ _client = self._get_ssh_client(host_i, host)
363
+ return _client.scp_send(local_file, remote_file, recurse=recurse)
377
364
 
378
365
  def _scp_recv(self, host_i, host, remote_file, local_file, recurse=False):
379
- self._get_ssh_client(host_i, host)
380
- return self._handle_greenlet_exc(
381
- self._host_clients[(host_i, host)].scp_recv, host,
382
- remote_file, local_file, recurse=recurse)
366
+ _client = self._get_ssh_client(host_i, host)
367
+ return _client.scp_recv(remote_file, local_file, recurse=recurse)
383
368
 
384
369
  def scp_send(self, local_file, remote_file, recurse=False, copy_args=None):
385
370
  """Copy local file to remote file in parallel via SCP.
@@ -404,6 +389,11 @@ class ParallelSSHClient(BaseParallelSSHClient):
404
389
  :type local_file: str
405
390
  :param remote_file: Remote filepath on remote host to copy file to
406
391
  :type remote_file: str
392
+ :param copy_args: (Optional) format local_file and remote_file strings
393
+ with per-host arguments in ``copy_args``. ``copy_args`` length must
394
+ equal length of host list -
395
+ :py:class:`pssh.exceptions.HostArgumentError` is raised otherwise
396
+ :type copy_args: tuple or list
407
397
  :param recurse: Whether or not to descend into directories recursively.
408
398
  :type recurse: bool
409
399
 
@@ -415,7 +405,7 @@ class ParallelSSHClient(BaseParallelSSHClient):
415
405
  """
416
406
  copy_args = [{'local_file': local_file,
417
407
  'remote_file': remote_file}
418
- for i, host in enumerate(self.hosts)] \
408
+ for _ in self.hosts] \
419
409
  if copy_args is None else copy_args
420
410
  local_file = "%(local_file)s"
421
411
  remote_file = "%(remote_file)s"