Skip to content

Commit 108d968

Browse files
author
Dan
committed
Added utility function to load all available key types. Updated docs. Updated import order so as not to break gevent/paramiko integration
1 parent 2b9e725 commit 108d968

File tree

4 files changed

+46
-15
lines changed

4 files changed

+46
-15
lines changed

pssh/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
2828
See :mod:`pssh.ParallelSSHClient` and :mod:`pssh.SSHClient` for class documentation.
2929
"""
30-
from .utils import enable_host_logger
30+
3131
from .pssh_client import ParallelSSHClient
3232
from .ssh_client import SSHClient
33+
from .utils import enable_host_logger
3334
from .exceptions import UnknownHostException, \
3435
AuthenticationException, ConnectionErrorException, SSHException
3536
import logging

pssh/pssh_client.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(self, hosts,
5050
user=None, password=None, port=None, pkey=None,
5151
forward_ssh_agent=True, num_retries=DEFAULT_RETRIES, timeout=120,
5252
pool_size=10, proxy_host=None, proxy_port=22,
53-
agent=None, host_config=None):
53+
agent=None, host_config=None, channel_timeout=None):
5454
"""
5555
:param hosts: Hosts to connect to
5656
:type hosts: list(str)
@@ -76,10 +76,9 @@ def __init__(self, hosts,
7676
Defaults to True if not set.
7777
:type forward_ssh_agent: bool
7878
:param pool_size: (Optional) Greenlet pool size. Controls on how many\
79-
hosts to execute tasks in parallel. Defaults to number of hosts or 10, \
80-
whichever is lower. Pool size will be *equal to* number of hosts if number\
81-
of hosts is lower than the pool size specified as that would only \
82-
increase overhead with no benefits.
79+
hosts to execute tasks in parallel. Defaults to 10. Values over 500 \
80+
are not likely to increase performance due to overhead in the single \
81+
thread running our event loop.
8382
:type pool_size: int
8483
:param proxy_host: (Optional) SSH host to tunnel connection through \
8584
so that SSH clients connect to self.host via client -> proxy_host -> \
@@ -94,6 +93,9 @@ def __init__(self, hosts,
9493
:param host_config: (Optional) Per-host configuration for cases where \
9594
not all hosts use the same configuration values.
9695
:type host_config: dict
96+
:param channel_timeout: (Optional) Time in seconds before an SSH operation \
97+
times out.
98+
:type channel_timeout: int
9799
98100
**Example Usage**
99101
@@ -244,6 +246,7 @@ def __init__(self, hosts,
244246
self.host_clients = {}
245247
self.agent = agent
246248
self.host_config = host_config if host_config else {}
249+
self.channel_timeout = channel_timeout
247250

248251
def run_command(self, *args, **kwargs):
249252
"""Run command on all hosts in parallel, honoring self.pool_size,
@@ -448,7 +451,7 @@ def _get_host_config_values(self, host):
448451
_password = self.host_config.get(host, {}).get('password', self.password)
449452
_pkey = self.host_config.get(host, {}).get('private_key', self.pkey)
450453
return _user, _port, _password, _pkey
451-
454+
452455
def _exec_command(self, host, *args, **kwargs):
453456
"""Make SSHClient, run command on host"""
454457
if not host in self.host_clients or not self.host_clients[host]:
@@ -461,7 +464,8 @@ def _exec_command(self, host, *args, **kwargs):
461464
timeout=self.timeout,
462465
proxy_host=self.proxy_host,
463466
proxy_port=self.proxy_port,
464-
agent=self.agent)
467+
agent=self.agent,
468+
channel_timeout=self.channel_timeout)
465469
return self.host_clients[host].exec_command(*args, **kwargs)
466470

467471
def get_output(self, cmd, output):
@@ -652,6 +656,7 @@ def _copy_file(self, host, local_file, remote_file, recurse=False):
652656
timeout=self.timeout,
653657
proxy_host=self.proxy_host,
654658
proxy_port=self.proxy_port,
655-
agent=self.agent)
659+
agent=self.agent,
660+
channel_timeout=self.channel_timeout)
656661
return self.host_clients[host].copy_file(local_file, remote_file,
657662
recurse=recurse)

pssh/ssh_client.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,14 @@
2020

2121
import sys
2222
from gevent import sleep
23-
import logging
2423
import paramiko
2524
from paramiko.ssh_exception import ChannelException as channel_exception
2625
import os
2726
from socket import gaierror as sock_gaierror, error as sock_error
2827
from .exceptions import UnknownHostException, AuthenticationException, \
2928
ConnectionErrorException, SSHException
3029
from .constants import DEFAULT_RETRIES
31-
30+
import logging
3231

3332
host_logger = logging.getLogger('pssh.host_logger')
3433
logger = logging.getLogger(__name__)
@@ -43,7 +42,7 @@ def __init__(self, host,
4342
user=None, password=None, port=None,
4443
pkey=None, forward_ssh_agent=True,
4544
num_retries=DEFAULT_RETRIES, agent=None, timeout=10,
46-
proxy_host=None, proxy_port=22):
45+
proxy_host=None, proxy_port=22, channel_timeout=None):
4746
"""Connect to host honouring any user set configuration in ~/.ssh/config \
4847
or /etc/ssh/ssh_config
4948
@@ -81,6 +80,9 @@ def __init__(self, host,
8180
:param proxy_port: (Optional) SSH port to use to login to proxy host if \
8281
set. Defaults to 22.
8382
:type proxy_port: int
83+
:param channel_timeout: (Optional) Time in seconds before an SSH operation \
84+
times out.
85+
:type channel_timeout: int
8486
"""
8587
ssh_config = paramiko.SSHConfig()
8688
_ssh_config_file = os.path.sep.join([os.path.expanduser('~'),
@@ -109,6 +111,7 @@ def __init__(self, host,
109111
self.client._agent = agent
110112
self.num_retries = num_retries
111113
self.timeout = timeout
114+
self.channel_timeout = channel_timeout
112115
self.proxy_host, self.proxy_port = proxy_host, proxy_port
113116
self.proxy_client = None
114117
if self.proxy_host and self.proxy_port:
@@ -117,7 +120,7 @@ def __init__(self, host,
117120
self._connect_tunnel()
118121
else:
119122
self._connect(self.client, self.host, self.port)
120-
123+
121124
def _connect_tunnel(self):
122125
"""Connects to SSH server via an intermediate SSH tunnel server.
123126
client (me) -> tunnel (ssh server to proxy through) -> \
@@ -211,8 +214,8 @@ def exec_command(self, command, sudo=False, user=None,
211214
if self.forward_ssh_agent:
212215
agent_handler = paramiko.agent.AgentRequestHandler(channel)
213216
channel.get_pty()
214-
# if self.timeout:
215-
# channel.settimeout(self.timeout)
217+
if self.channel_timeout:
218+
channel.settimeout(self.channel_timeout)
216219
_stdout, _stderr = channel.makefile('rb'), \
217220
channel.makefile_stderr('rb')
218221
stdout, stderr = self._read_output_buffer(_stdout,), \

pssh/utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020

2121

2222
import logging
23+
import gevent
24+
from paramiko.rsakey import RSAKey
25+
from paramiko.dsskey import DSSKey
26+
from paramiko.ecdsakey import ECDSAKey
27+
from paramiko import SSHException
2328

2429
host_logger = logging.getLogger('pssh.host_logger')
2530
logger = logging.getLogger('pssh')
@@ -42,6 +47,23 @@ def enable_host_logger():
4247
as it becomes available"""
4348
enable_logger(host_logger)
4449

50+
def load_private_key(pkey):
51+
"""Load private key from pkey file object or filename
52+
53+
:param pkey: File object or file name containing private key
54+
:type pkey: file/str"""
55+
pkey = None
56+
if not isinstance(pkey, file):
57+
pkey = open(pkey)
58+
for keytype in [RSAKey, DSSKey, ECDSAKey]:
59+
try:
60+
pkey = keytype.from_private_key(pkey)
61+
except SSHException:
62+
pass
63+
if not pkey:
64+
logger.error("Failed to load private key using all available key types - giving up..")
65+
return pkey
66+
4567
# def enable_pssh_logger():
4668
# """Enable parallel-ssh's logger to stdout"""
4769
# enable_logger(logger)

0 commit comments

Comments
 (0)