parallel-ssh 2.13.0rc1__tar.gz → 2.15.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 (41) hide show
  1. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/PKG-INFO +19 -6
  2. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/PKG-INFO +19 -6
  3. parallel_ssh-2.15.0/pssh/__init__.py +39 -0
  4. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/_version.py +3 -3
  5. parallel_ssh-2.15.0/pssh/clients/__init__.py +19 -0
  6. parallel_ssh-2.15.0/pssh/clients/base/__init__.py +16 -0
  7. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/base/parallel.py +19 -28
  8. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/base/single.py +105 -73
  9. parallel_ssh-2.15.0/pssh/clients/common.py +51 -0
  10. parallel_ssh-2.15.0/pssh/clients/native/__init__.py +20 -0
  11. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/parallel.py +26 -13
  12. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/single.py +155 -77
  13. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/tunnel.py +77 -46
  14. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/reader.py +13 -13
  15. parallel_ssh-2.15.0/pssh/clients/ssh/__init__.py +20 -0
  16. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/ssh/parallel.py +18 -13
  17. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/ssh/single.py +49 -32
  18. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/config.py +35 -14
  19. parallel_ssh-2.15.0/pssh/constants.py +28 -0
  20. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/exceptions.py +17 -13
  21. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/output.py +13 -13
  22. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/utils.py +13 -13
  23. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/setup.cfg +1 -1
  24. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/setup.py +21 -15
  25. parallel_ssh-2.13.0rc1/pssh/__init__.py +0 -39
  26. parallel_ssh-2.13.0rc1/pssh/clients/__init__.py +0 -19
  27. parallel_ssh-2.13.0rc1/pssh/clients/base/__init__.py +0 -0
  28. parallel_ssh-2.13.0rc1/pssh/clients/common.py +0 -41
  29. parallel_ssh-2.13.0rc1/pssh/clients/native/__init__.py +0 -20
  30. parallel_ssh-2.13.0rc1/pssh/clients/ssh/__init__.py +0 -20
  31. parallel_ssh-2.13.0rc1/pssh/constants.py +0 -28
  32. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/COPYING +0 -0
  33. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/COPYING.LESSER +0 -0
  34. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/LICENSE +0 -0
  35. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/MANIFEST.in +0 -0
  36. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/README.rst +0 -0
  37. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/SOURCES.txt +0 -0
  38. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/dependency_links.txt +0 -0
  39. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/requires.txt +0 -0
  40. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/top_level.txt +0 -0
  41. {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/versioneer.py +0 -0
@@ -1,25 +1,29 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: parallel-ssh
3
- Version: 2.13.0rc1
3
+ Version: 2.15.0
4
4
  Summary: Asynchronous parallel SSH library
5
5
  Home-page: https://github.com/ParallelSSH/parallel-ssh
6
6
  Author: Panos Kittenis
7
- Author-email: zuboci@yandex.com
8
- License: LGPLv2.1
7
+ Author-email: danst@tutanota.com
8
+ License: LGPL-2.1-only
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python
14
12
  Classifier: Programming Language :: Python :: 3
15
13
  Classifier: Programming Language :: Python :: 3.8
16
14
  Classifier: Programming Language :: Python :: 3.9
17
15
  Classifier: Programming Language :: Python :: 3.10
18
16
  Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: System :: Shells
20
23
  Classifier: Topic :: System :: Networking
21
24
  Classifier: Topic :: Software Development :: Libraries
22
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Operating System :: POSIX
23
27
  Classifier: Operating System :: POSIX :: Linux
24
28
  Classifier: Operating System :: POSIX :: BSD
25
29
  Classifier: Operating System :: Microsoft :: Windows
@@ -30,6 +34,15 @@ License-File: COPYING.LESSER
30
34
  Requires-Dist: gevent
31
35
  Requires-Dist: ssh2-python
32
36
  Requires-Dist: ssh-python
37
+ Dynamic: author
38
+ Dynamic: author-email
39
+ Dynamic: classifier
40
+ Dynamic: description
41
+ Dynamic: home-page
42
+ Dynamic: license
43
+ Dynamic: license-file
44
+ Dynamic: requires-dist
45
+ Dynamic: summary
33
46
 
34
47
  ============
35
48
  parallel-ssh
@@ -1,25 +1,29 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: parallel-ssh
3
- Version: 2.13.0rc1
3
+ Version: 2.15.0
4
4
  Summary: Asynchronous parallel SSH library
5
5
  Home-page: https://github.com/ParallelSSH/parallel-ssh
6
6
  Author: Panos Kittenis
7
- Author-email: zuboci@yandex.com
8
- License: LGPLv2.1
7
+ Author-email: danst@tutanota.com
8
+ License: LGPL-2.1-only
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
- Classifier: License :: OSI Approved :: GNU Lesser General Public License v2 (LGPLv2)
11
10
  Classifier: Intended Audience :: Developers
12
11
  Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python
14
12
  Classifier: Programming Language :: Python :: 3
15
13
  Classifier: Programming Language :: Python :: 3.8
16
14
  Classifier: Programming Language :: Python :: 3.9
17
15
  Classifier: Programming Language :: Python :: 3.10
18
16
  Classifier: Programming Language :: Python :: 3.11
19
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: System :: Shells
20
23
  Classifier: Topic :: System :: Networking
21
24
  Classifier: Topic :: Software Development :: Libraries
22
25
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
26
+ Classifier: Operating System :: POSIX
23
27
  Classifier: Operating System :: POSIX :: Linux
24
28
  Classifier: Operating System :: POSIX :: BSD
25
29
  Classifier: Operating System :: Microsoft :: Windows
@@ -30,6 +34,15 @@ License-File: COPYING.LESSER
30
34
  Requires-Dist: gevent
31
35
  Requires-Dist: ssh2-python
32
36
  Requires-Dist: ssh-python
37
+ Dynamic: author
38
+ Dynamic: author-email
39
+ Dynamic: classifier
40
+ Dynamic: description
41
+ Dynamic: home-page
42
+ Dynamic: license
43
+ Dynamic: license-file
44
+ Dynamic: requires-dist
45
+ Dynamic: summary
33
46
 
34
47
  ============
35
48
  parallel-ssh
@@ -0,0 +1,39 @@
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ """Asynchronous parallel SSH client library.
19
+
20
+ Run SSH commands over many - hundreds/hundreds of thousands - number of servers
21
+ asynchronously and with minimal system load on the client host.
22
+
23
+ New users should start with `pssh.clients.ParallelSSHClient.run_command` and
24
+ `pssh.clients.SSHClient.run_command`
25
+
26
+ See also `pssh.clients.ParallelSSHClient` and pssh.clients.SSHClient`
27
+ for class documentation.
28
+ """
29
+
30
+
31
+ from logging import getLogger, NullHandler
32
+ from . import _version
33
+ __version__ = _version.get_versions()['version']
34
+ del _version
35
+
36
+ host_logger = getLogger('pssh.host_logger')
37
+ logger = getLogger('pssh')
38
+ host_logger.addHandler(NullHandler())
39
+ logger.addHandler(NullHandler())
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-01-13T03:04:42+0000",
11
+ "date": "2025-10-12T14:04:45+0100",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "f1d46fcf86c331b21dc199f5129f59da3bc1284e",
15
- "version": "2.13.0rc1"
14
+ "full-revisionid": "8fa5a91bbfb7cd87148a566cc3772006ec2ba989",
15
+ "version": "2.15.0"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,19 @@
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ # flake8: noqa: F401
19
+ from .native import ParallelSSHClient, SSHClient
@@ -0,0 +1,16 @@
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
@@ -1,19 +1,19 @@
1
- # This file is part of parallel-ssh.
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
2
4
  #
3
- # Copyright (C) 2014-2022 Panos Kittenis and contributors.
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
4
8
  #
5
- # This library is free software; you can redistribute it and/or
6
- # modify it under the terms of the GNU Lesser General Public
7
- # License as published by the Free Software Foundation, version 2.1.
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
8
13
  #
9
- # This library is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
- # Lesser General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Lesser General Public
15
- # License along with this library; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
  """Abstract parallel SSH client package"""
19
19
 
@@ -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, _validate_pkey
26
+ from ..common import _validate_pkey_path, _validate_pkey, _validate_api
27
27
  from ...config import HostConfig
28
28
  from ...constants import DEFAULT_RETRIES, RETRY_DELAY
29
29
  from ...exceptions import HostArgumentError, Timeout, ShellError, HostConfigError
@@ -55,6 +55,8 @@ class BaseParallelSSHClient(object):
55
55
  gssapi_client_identity=None,
56
56
  gssapi_delegate_credentials=False,
57
57
  forward_ssh_agent=False,
58
+ compress=False,
59
+ keyboard_interactive=False,
58
60
  _auth_thread_pool=True,
59
61
  ):
60
62
  self.allow_agent = allow_agent
@@ -86,6 +88,9 @@ class BaseParallelSSHClient(object):
86
88
  self.gssapi_server_identity = gssapi_server_identity
87
89
  self.gssapi_client_identity = gssapi_client_identity
88
90
  self.gssapi_delegate_credentials = gssapi_delegate_credentials
91
+ self.compress = compress
92
+ self.keyboard_interactive = keyboard_interactive
93
+ _validate_api(self.keyboard_interactive, self.password)
89
94
  self._auth_thread_pool = _auth_thread_pool
90
95
  self._check_host_config()
91
96
 
@@ -114,20 +119,6 @@ class BaseParallelSSHClient(object):
114
119
  self._host_clients.pop((i, host), None)
115
120
  self._hosts = _hosts
116
121
 
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
-
131
122
  def _check_host_config(self):
132
123
  if self.host_config is None:
133
124
  return
@@ -1,33 +1,36 @@
1
- # This file is part of parallel-ssh.
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
2
4
  #
3
- # Copyright (C) 2014-2022 Panos Kittenis and contributors.
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
4
8
  #
5
- # This library is free software; you can redistribute it and/or
6
- # modify it under the terms of the GNU Lesser General Public
7
- # License as published by the Free Software Foundation, version 2.1.
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
8
13
  #
9
- # This library is distributed in the hope that it will be useful,
10
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
- # Lesser General Public License for more details.
13
- #
14
- # You should have received a copy of the GNU Lesser General Public
15
- # License along with this library; if not, write to the Free Software
16
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
  import logging
19
19
  import os
20
20
  from getpass import getuser
21
21
  from socket import gaierror as sock_gaierror, error as sock_error
22
+ from warnings import warn
22
23
 
23
24
  from gevent import sleep, socket, Timeout as GTimeout
24
25
  from gevent.hub import Hub
26
+ from gevent.pool import Pool
25
27
  from gevent.select import poll, POLLIN, POLLOUT
28
+ from gevent.socket import SHUT_RDWR
26
29
  from ssh2.exceptions import AgentConnectionError, AgentListIdentitiesError, \
27
30
  AgentAuthenticationError, AgentGetIdentityError
28
31
  from ssh2.utils import find_eol
29
32
 
30
- from ..common import _validate_pkey
33
+ from ..common import _validate_pkey, _validate_api
31
34
  from ..reader import ConcurrentRWBuffer
32
35
  from ...constants import DEFAULT_RETRIES, RETRY_DELAY
33
36
  from ...exceptions import UnknownHostError, AuthenticationError, \
@@ -39,6 +42,61 @@ host_logger = logging.getLogger('pssh.host_logger')
39
42
  logger = logging.getLogger(__name__)
40
43
 
41
44
 
45
+ class PollMixIn(object):
46
+ """MixIn for co-operative socket polling functionality.
47
+
48
+ """
49
+ __slots__ = ('sock',)
50
+
51
+ def __init__(self, sock=None):
52
+ self.sock = sock
53
+
54
+ def poll(self, timeout=None):
55
+ raise NotImplementedError
56
+
57
+ def eagain(self, func, *args, **kwargs):
58
+ raise NotImplementedError
59
+
60
+ def eagain_write(self, write_func, data):
61
+ raise NotImplementedError
62
+
63
+ def _poll_errcodes(self, directions_func, inbound, outbound):
64
+ directions = directions_func()
65
+ if directions == 0:
66
+ return
67
+ events = 0
68
+ if directions & inbound:
69
+ events = POLLIN
70
+ if directions & outbound:
71
+ events |= POLLOUT
72
+ self._poll_socket(events)
73
+
74
+ def _poll_socket(self, events):
75
+ if self.sock is None:
76
+ return
77
+ poller = poll()
78
+ poller.register(self.sock, eventmask=events)
79
+ poller.poll(timeout=1)
80
+
81
+ def _eagain_errcode(self, func, eagain, *args, **kwargs):
82
+ ret = func(*args, **kwargs)
83
+ while ret == eagain:
84
+ self.poll()
85
+ ret = func(*args, **kwargs)
86
+ sleep()
87
+ return ret
88
+
89
+ def _eagain_write_errcode(self, write_func, data, eagain):
90
+ data_len = len(data)
91
+ total_written = 0
92
+ while total_written < data_len:
93
+ rc, bytes_written = write_func(data[total_written:])
94
+ total_written += bytes_written
95
+ if rc == eagain:
96
+ self.poll()
97
+ sleep()
98
+
99
+
42
100
  class Stdin(object):
43
101
  """Stdin stream for a channel.
44
102
 
@@ -64,13 +122,13 @@ class Stdin(object):
64
122
  :param data: Data to write.
65
123
  :type data: str
66
124
  """
67
- return self._client._eagain(self._channel.write, data)
125
+ return self._client.eagain(self._channel.write, data)
68
126
 
69
127
  def flush(self):
70
128
  """Flush pending data written to stdin."""
71
129
  if not hasattr(self._channel, "flush"):
72
130
  return
73
- return self._client._eagain(self._channel.flush)
131
+ return self._client.eagain(self._channel.flush)
74
132
 
75
133
 
76
134
  class InteractiveShell(object):
@@ -132,7 +190,7 @@ class InteractiveShell(object):
132
190
  """Wait for shell to finish executing and close channel."""
133
191
  if self._chan is None:
134
192
  return
135
- self._client._eagain(self._chan.send_eof)
193
+ self._client.eagain(self._chan.send_eof)
136
194
  self._client.wait_finished(self.output)
137
195
  return self
138
196
 
@@ -144,10 +202,10 @@ class InteractiveShell(object):
144
202
  :type cmd: str
145
203
  """
146
204
  cmd = cmd.encode(self._encoding) + self._EOL
147
- self._client._eagain_write(self._chan.write, cmd)
205
+ self._client.eagain_write(self._chan.write, cmd)
148
206
 
149
207
 
150
- class BaseSSHClient(object):
208
+ class BaseSSHClient(PollMixIn):
151
209
 
152
210
  IDENTITIES = (
153
211
  os.path.expanduser('~/.ssh/id_rsa'),
@@ -168,15 +226,18 @@ class BaseSSHClient(object):
168
226
  _auth_thread_pool=True,
169
227
  identity_auth=True,
170
228
  ipv6_only=False,
229
+ compress=False,
230
+ keyboard_interactive=False,
171
231
  ):
232
+ super(PollMixIn, self).__init__()
172
233
  self._auth_thread_pool = _auth_thread_pool
173
234
  self.host = host
174
235
  self.alias = alias
175
236
  self.user = user if user else getuser()
176
237
  self.password = password
238
+ self.keyboard_interactive = keyboard_interactive
177
239
  self.port = port if port else 22
178
240
  self.num_retries = num_retries
179
- self.sock = None
180
241
  self.timeout = timeout if timeout else None
181
242
  self.retry_delay = retry_delay
182
243
  self.allow_agent = allow_agent
@@ -187,6 +248,10 @@ class BaseSSHClient(object):
187
248
  self.identity_auth = identity_auth
188
249
  self._keepalive_greenlet = None
189
250
  self.ipv6_only = ipv6_only
251
+ self.compress = compress
252
+ self.keyboard_interactive = keyboard_interactive
253
+ _validate_api(self.keyboard_interactive, self.password)
254
+ self._pool = Pool()
190
255
  self._init()
191
256
 
192
257
  def _pkey_from_memory(self, pkey_data):
@@ -212,11 +277,15 @@ class BaseSSHClient(object):
212
277
  raise AuthenticationError(msg, self.host, self.port, ex, retries, self.num_retries)
213
278
 
214
279
  def disconnect(self):
280
+ """Deprecated and a no-op. Disconnections handled by client de-allocation."""
281
+ warn("Deprecated and a no-op - to be removed in future releases.", DeprecationWarning)
282
+
283
+ def _disconnect(self):
215
284
  raise NotImplementedError
216
285
 
217
286
  def __del__(self):
218
287
  try:
219
- self.disconnect()
288
+ self._disconnect()
220
289
  except Exception:
221
290
  pass
222
291
 
@@ -224,7 +293,7 @@ class BaseSSHClient(object):
224
293
  return self
225
294
 
226
295
  def __exit__(self, *args):
227
- self.disconnect()
296
+ self._disconnect()
228
297
 
229
298
  def open_shell(self, encoding='utf-8', read_timeout=None):
230
299
  """Open interactive shell on new channel.
@@ -244,7 +313,8 @@ class BaseSSHClient(object):
244
313
  raise NotImplementedError
245
314
 
246
315
  def _disconnect_eagain(self):
247
- self._eagain(self.session.disconnect)
316
+ if self.session is not None and self.sock is not None and not self.sock.closed:
317
+ self.eagain(self.session.disconnect)
248
318
 
249
319
  def _connect_init_session_retry(self, retries):
250
320
  try:
@@ -254,9 +324,11 @@ class BaseSSHClient(object):
254
324
  self.session = None
255
325
  if not self.sock.closed:
256
326
  try:
257
- self.sock.close()
327
+ self.sock.shutdown(SHUT_RDWR)
328
+ self.sock.detach()
258
329
  except Exception:
259
330
  pass
331
+ self.sock = None
260
332
  sleep(self.retry_delay)
261
333
  self._connect(self._host, self._port, retries=retries)
262
334
  return self._init_session(retries=retries)
@@ -371,7 +443,7 @@ class BaseSSHClient(object):
371
443
  msg = "No remaining authentication methods"
372
444
  logger.error(msg)
373
445
  raise AuthenticationError(msg)
374
- logger.debug("Private key auth failed, trying password")
446
+ logger.debug("Private key auth failed or not enabled, trying password")
375
447
  self._password_auth()
376
448
 
377
449
  def _agent_auth(self):
@@ -420,6 +492,13 @@ class BaseSSHClient(object):
420
492
  raise NotImplementedError
421
493
 
422
494
  def execute(self, cmd, use_pty=False, channel=None):
495
+ """
496
+ Deprecated - use ``run_command`` instead which returns a ``HostOutput`` object.
497
+ """
498
+ warn("Deprecated - use run_command instead.", DeprecationWarning)
499
+ return self._execute(cmd, use_pty=use_pty, channel=channel)
500
+
501
+ def _execute(self, cmd, use_pty=False, channel=None):
423
502
  raise NotImplementedError
424
503
 
425
504
  def read_stderr(self, stderr_buffer, timeout=None):
@@ -554,37 +633,11 @@ class BaseSSHClient(object):
554
633
  _command += "%s '%s'" % (_shell, command,)
555
634
  _command = _command.encode(encoding)
556
635
  with GTimeout(seconds=self.timeout):
557
- channel = self.execute(_command, use_pty=use_pty)
636
+ channel = self._execute(_command, use_pty=use_pty)
558
637
  _timeout = read_timeout if read_timeout else timeout
559
638
  host_out = self._make_host_output(channel, encoding, _timeout)
560
639
  return host_out
561
640
 
562
- def _eagain_write_errcode(self, write_func, data, eagain):
563
- data_len = len(data)
564
- total_written = 0
565
- while total_written < data_len:
566
- rc, bytes_written = write_func(data[total_written:])
567
- total_written += bytes_written
568
- if rc == eagain:
569
- self.poll()
570
- sleep()
571
-
572
- def _eagain_errcode(self, func, eagain, *args, **kwargs):
573
- timeout = kwargs.pop('timeout', self.timeout)
574
- with GTimeout(seconds=timeout, exception=Timeout):
575
- ret = func(*args, **kwargs)
576
- while ret == eagain:
577
- self.poll()
578
- ret = func(*args, **kwargs)
579
- sleep()
580
- return ret
581
-
582
- def _eagain_write(self, write_func, data):
583
- raise NotImplementedError
584
-
585
- def _eagain(self, func, *args, **kwargs):
586
- raise NotImplementedError
587
-
588
641
  def _make_sftp(self):
589
642
  raise NotImplementedError
590
643
 
@@ -692,24 +745,3 @@ class BaseSSHClient(object):
692
745
  _sep = file_path.rfind('/')
693
746
  if _sep > 0:
694
747
  return file_path[:_sep]
695
-
696
- def poll(self):
697
- raise NotImplementedError
698
-
699
- def _poll_socket(self, events):
700
- if self.sock is None:
701
- return
702
- poller = poll()
703
- poller.register(self.sock, eventmask=events)
704
- poller.poll(timeout=1)
705
-
706
- def _poll_errcodes(self, directions_func, inbound, outbound):
707
- directions = directions_func()
708
- if directions == 0:
709
- return
710
- events = 0
711
- if directions & inbound:
712
- events = POLLIN
713
- if directions & outbound:
714
- events |= POLLOUT
715
- self._poll_socket(events)
@@ -0,0 +1,51 @@
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ import logging
19
+ import os
20
+
21
+ from ..exceptions import PKeyFileError, InvalidAPIUseError
22
+
23
+ logger = logging.getLogger('pssh')
24
+
25
+
26
+ def _validate_pkey_path(pkey):
27
+ if pkey is None:
28
+ return
29
+ pkey = os.path.normpath(os.path.expanduser(pkey))
30
+ if not os.path.exists(pkey):
31
+ msg = "File %s does not exist. " \
32
+ "Please use either absolute or relative to user directory " \
33
+ "paths like '~/.ssh/my_key' for pkey parameter"
34
+ ex = PKeyFileError(msg, pkey)
35
+ raise ex
36
+ return pkey
37
+
38
+
39
+ def _validate_pkey(pkey):
40
+ if pkey is None:
41
+ return
42
+ if isinstance(pkey, str):
43
+ return _validate_pkey_path(pkey)
44
+ return pkey
45
+
46
+
47
+ def _validate_api(keyboard_interactive, password):
48
+ if keyboard_interactive and not password:
49
+ msg = "Keyboard interactive authentication is enabled but no password is provided - cannot continue"
50
+ logger.error(msg)
51
+ raise InvalidAPIUseError(msg)
@@ -0,0 +1,20 @@
1
+ # This file is part of parallel-ssh.
2
+ # Copyright (C) 2014-2025 Panos Kittenis.
3
+ # Copyright (C) 2014-2025 parallel-ssh Contributors.
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation, version 2.1.
8
+ #
9
+ # This library is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
+
18
+ # flake8: noqa: F401
19
+ from .parallel import ParallelSSHClient
20
+ from .single import SSHClient, logger