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.
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/PKG-INFO +19 -6
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/PKG-INFO +19 -6
- parallel_ssh-2.15.0/pssh/__init__.py +39 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/_version.py +3 -3
- parallel_ssh-2.15.0/pssh/clients/__init__.py +19 -0
- parallel_ssh-2.15.0/pssh/clients/base/__init__.py +16 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/base/parallel.py +19 -28
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/base/single.py +105 -73
- parallel_ssh-2.15.0/pssh/clients/common.py +51 -0
- parallel_ssh-2.15.0/pssh/clients/native/__init__.py +20 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/parallel.py +26 -13
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/single.py +155 -77
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/native/tunnel.py +77 -46
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/reader.py +13 -13
- parallel_ssh-2.15.0/pssh/clients/ssh/__init__.py +20 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/ssh/parallel.py +18 -13
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/clients/ssh/single.py +49 -32
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/config.py +35 -14
- parallel_ssh-2.15.0/pssh/constants.py +28 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/exceptions.py +17 -13
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/output.py +13 -13
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/pssh/utils.py +13 -13
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/setup.cfg +1 -1
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/setup.py +21 -15
- parallel_ssh-2.13.0rc1/pssh/__init__.py +0 -39
- parallel_ssh-2.13.0rc1/pssh/clients/__init__.py +0 -19
- parallel_ssh-2.13.0rc1/pssh/clients/base/__init__.py +0 -0
- parallel_ssh-2.13.0rc1/pssh/clients/common.py +0 -41
- parallel_ssh-2.13.0rc1/pssh/clients/native/__init__.py +0 -20
- parallel_ssh-2.13.0rc1/pssh/clients/ssh/__init__.py +0 -20
- parallel_ssh-2.13.0rc1/pssh/constants.py +0 -28
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/COPYING +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/COPYING.LESSER +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/LICENSE +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/MANIFEST.in +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/README.rst +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/SOURCES.txt +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/dependency_links.txt +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/requires.txt +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/parallel_ssh.egg-info/top_level.txt +0 -0
- {parallel_ssh-2.13.0rc1 → parallel_ssh-2.15.0}/versioneer.py +0 -0
@@ -1,25 +1,29 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: parallel-ssh
|
3
|
-
Version: 2.
|
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:
|
8
|
-
License:
|
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
|
+
Metadata-Version: 2.4
|
2
2
|
Name: parallel-ssh
|
3
|
-
Version: 2.
|
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:
|
8
|
-
License:
|
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-
|
11
|
+
"date": "2025-10-12T14:04:45+0100",
|
12
12
|
"dirty": false,
|
13
13
|
"error": null,
|
14
|
-
"full-revisionid": "
|
15
|
-
"version": "2.
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
6
|
-
#
|
7
|
-
#
|
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
|
-
#
|
10
|
-
#
|
11
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
6
|
-
#
|
7
|
-
#
|
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
|
-
#
|
10
|
-
#
|
11
|
-
#
|
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.
|
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.
|
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.
|
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.
|
205
|
+
self._client.eagain_write(self._chan.write, cmd)
|
148
206
|
|
149
207
|
|
150
|
-
class BaseSSHClient(
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|