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.
- parallel_ssh-2.13.0/MANIFEST.in +14 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/PKG-INFO +9 -12
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/README.rst +3 -6
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/PKG-INFO +9 -12
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/requires.txt +1 -1
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/__init__.py +3 -3
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/_version.py +4 -4
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/__init__.py +2 -3
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/parallel.py +38 -27
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/single.py +22 -14
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/parallel.py +11 -21
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/single.py +56 -32
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/tunnel.py +2 -7
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/reader.py +24 -9
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/parallel.py +1 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/single.py +26 -28
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/config.py +7 -2
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/output.py +10 -6
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/setup.cfg +1 -1
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/setup.py +3 -3
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/versioneer.py +776 -346
- parallel-ssh-2.11.1/MANIFEST.in +0 -6
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/COPYING +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/COPYING.LESSER +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/LICENSE +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/SOURCES.txt +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/dependency_links.txt +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/parallel_ssh.egg-info/top_level.txt +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/base/__init__.py +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/common.py +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/native/__init__.py +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/clients/ssh/__init__.py +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/constants.py +0 -0
- {parallel-ssh-2.11.1 → parallel_ssh-2.13.0}/pssh/exceptions.py +0 -0
- {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.
|
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
|
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
|
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
|
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
|
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.
|
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
|
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
|
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.
|
@@ -29,9 +29,9 @@ for class documentation.
|
|
29
29
|
|
30
30
|
|
31
31
|
from logging import getLogger, NullHandler
|
32
|
-
from .
|
33
|
-
__version__ = get_versions()['version']
|
34
|
-
del
|
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.
|
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": "
|
11
|
+
"date": "2025-01-13T03:04:42+0000",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "2.
|
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-
|
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
|
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=
|
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:
|
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
|
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
|
331
|
+
for _ in stdout:
|
318
332
|
pass
|
319
|
-
for
|
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
|
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
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
return
|
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,
|
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,
|
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
|
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
|
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
|
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
|
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
|
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"
|