Skip to content

Commit 27b34dc

Browse files
committed
Added AuthenticationException in pssh module so as not to have to import from paramiko to handle it. Code quality changes.
1 parent cf21452 commit 27b34dc

File tree

1 file changed

+52
-26
lines changed

1 file changed

+52
-26
lines changed

pssh.py

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
logger = logging.getLogger(__name__)
2222

23+
2324
def _setup_logger(_logger):
2425
"""Setup default logger"""
2526
_handler = logging.StreamHandler()
@@ -28,21 +29,36 @@ def _setup_logger(_logger):
2829
_logger.addHandler(handler)
2930
_logger.setLevel(logging.DEBUG)
3031

31-
class UnknownHostException(Exception): pass
32-
class ConnectionErrorException(Exception): pass
32+
33+
class UnknownHostException(Exception):
34+
"""Raised when a host is unknown (dns failure)"""
35+
pass
36+
37+
38+
class ConnectionErrorException(Exception):
39+
"""Raised on error connecting (connection refused/timed out)"""
40+
pass
41+
42+
43+
class AuthenticationException(Exception):
44+
"""Raised on authentication error (user/password/ssh key error"""
45+
pass
46+
3347

3448
class SSHClient(object):
3549
"""Wrapper class over paramiko.SSHClient with sane defaults"""
3650

3751
def __init__(self, host,
38-
user = None,
39-
password = None, port = None):
40-
"""Connect to host honoring any user set configuration in ~/.ssh/config or /etc/ssh/ssh_config
52+
user=None,
53+
password=None, port=None):
54+
"""Connect to host honoring any user set configuration in ~/.ssh/config
55+
or /etc/ssh/ssh_config
4156
:type: str
4257
:param host: Hostname to connect to
4358
:type str:
44-
:param user: (Optional) User to login as. Defaults to logged in user or user from ~/.ssh/config if set
45-
:throws: paramiko.AuthenticationException on authentication error
59+
:param user: (Optional) User to login as. Defaults to logged in user or
60+
user from ~/.ssh/config if set
61+
:throws: ssh_client.AuthenticationException on authentication error
4662
:throws: ssh_client.UnknownHostException on DNS resolution error
4763
:throws: ssh_client.ConnectionErrorException on error connecting"""
4864
ssh_config = paramiko.SSHConfig()
@@ -75,48 +91,55 @@ def __init__(self, host,
7591
def _connect(self):
7692
"""Connect to host, throw UnknownHost exception on DNS errors"""
7793
try:
78-
self.client.connect(self.host, username=self.user, password=self.password, port = self.port)
94+
self.client.connect(self.host, username=self.user,
95+
password=self.password, port=self.port)
7996
except socket.gaierror, e:
80-
logger.error("Could not resolve host '%s'" % (self.host,))
81-
raise UnknownHostException("%s - %s" % (str(e.args[1]), self.host,))
97+
logger.error("Could not resolve host '%s'", self.host,)
98+
raise UnknownHostException("%s - %s" % (str(e.args[1]),
99+
self.host,))
82100
except socket.error, e:
83-
logger.error("Error connecting to host '%s'" % (self.host,))
84-
raise ConnectionErrorException("%s for host '%s'" % (str(e.args[1]), self.host,))
101+
logger.error("Error connecting to host '%s'", self.host,)
102+
raise ConnectionErrorException("%s for host '%s'" % (str(e.args[1]),
103+
self.host,))
104+
except paramiko.AuthenticationException, e:
105+
raise AuthenticationException(e)
85106

86-
def exec_command(self, command, sudo = False, **kwargs):
107+
def exec_command(self, command, sudo=False, **kwargs):
87108
"""Wrapper to paramiko.SSHClient.exec_command"""
88-
89109
channel = self.client.get_transport().open_session()
90110
channel.get_pty()
91-
_, stdout, stderr = channel.makefile('wb'), channel.makefile('rb'), channel.makefile_stderr('rb')
111+
(_, stdout, stderr) = (channel.makefile('wb'), channel.makefile('rb'),
112+
channel.makefile_stderr('rb'))
92113
if sudo:
93114
command = 'sudo -S bash -c "%s"' % command.replace('"', '\\"')
94-
logger.debug("Running command %s on %s" % (command, self.host))
115+
logger.debug("Running command %s on %s", command, self.host)
95116
channel.exec_command(command, **kwargs)
96117
logger.debug("Command finished executing")
97118
while not channel.recv_ready():
98119
gevent.sleep(.2)
99120
return channel, self.host, stdout, stderr
100121

122+
101123
class ParallelSSHClient(object):
102124
"""Uses SSHClient, runs command on multiple hosts in parallel"""
103125

104126
def __init__(self, hosts,
105-
user = None, password = None, port = None,
106-
pool_size = 10):
127+
user=None, password=None, port=None,
128+
pool_size=10):
107129
"""Connect to hosts
108130
:type: list(str)
109131
:param hosts: Hosts to connect to
110132
:type: int
111133
:param pool_size: Pool size - how many commands to run in parallel
112134
:type str:
113-
:param user: (Optional) User to login as. Defaults to logged in user or user from ~/.ssh/config if set
135+
:param user: (Optional) User to login as. Defaults to logged in user or
136+
user from ~/.ssh/config if set
114137
:type str:
115138
:param password: (Optional) Password to use for login
116139
:throws: paramiko.AuthenticationException on authentication error
117140
:throws: ssh_client.UnknownHostException on DNS resolution error
118141
:throws: ssh_client.ConnectionErrorException on error connecting"""
119-
self.pool = gevent.pool.Pool(size = pool_size)
142+
self.pool = gevent.pool.Pool(size=pool_size)
120143
self.pool_size = pool_size
121144
self.hosts = hosts
122145
self.user = user
@@ -127,24 +150,27 @@ def __init__(self, hosts,
127150

128151
def exec_command(self, *args, **kwargs):
129152
"""Run command on all hosts in parallel, honoring self.pool_size"""
130-
return [self.pool.spawn(self._exec_command, host, *args, **kwargs) for host in self.hosts]
153+
return [self.pool.spawn(self._exec_command, host, *args, **kwargs)
154+
for host in self.hosts]
131155

132156
def _exec_command(self, host, *args, **kwargs):
133157
"""Make SSHClient, run command on host"""
134158
if not self.host_clients[host]:
135-
self.host_clients[host] = SSHClient(host, user = self.user, password = self.password,
136-
port = self.port)
159+
self.host_clients[host] = SSHClient(host, user=self.user,
160+
password=self.password,
161+
port=self.port)
137162
return self.host_clients[host].exec_command(*args, **kwargs)
138163

139164
def get_stdout(self, greenlet):
140165
"""Print stdout from greenlet and return exit code for host"""
141166
channel, host, stdout, stderr = greenlet.get()
142167
for line in stdout:
143-
host_logger.info("[%s]\t%s" % (host, line.strip(),))
168+
host_logger.info("[%s]\t%s", host, line.strip(),)
144169
for line in stderr:
145-
host_logger.info("[%s] [err] %s" % (host, line.strip(),))
170+
host_logger.info("[%s] [err] %s", host, line.strip(),)
146171
channel.close()
147-
return {host : {'exit_code' : channel.recv_exit_status()}}
172+
return {host: {'exit_code': channel.recv_exit_status()}}
173+
148174

149175
def test():
150176
client = SSHClient('localhost')

0 commit comments

Comments
 (0)