Skip to content

Commit 2cebb15

Browse files
author
Dan
committed
Added unittests for loading all possible key types plus invalid key. Updated load private key function. Added unittest for per host configuration in pssh client. Resolves #20
1 parent 108d968 commit 2cebb15

File tree

6 files changed

+97
-13
lines changed

6 files changed

+97
-13
lines changed

pssh/pssh_client.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,21 @@ def __init__(self, hosts,
201201
202202
**Per-Host configuration**
203203
204+
Per host configuration can be provided for any of user, password port
205+
and private key. Private key value is a :mod:`paramiko.PKey` object as
206+
returned by :mod:`pssh.utils.load_private_key`.
204207
208+
:mod:`pssh.utils.load_private_key` accepts both file names and file-like
209+
objects and will attempt to load all available key types, returning
210+
`None` if they all fail.
211+
212+
>>> from pssh.utils import load_private_key
205213
>>> host_config = { 'host1' : {'user': 'user1', 'password': 'pass',
206-
... 'port': 2222, 'private_key': 'my_key.pem'},
214+
... 'port': 2222,
215+
... 'private_key': load_private_key('my_key.pem')},
207216
... 'host2' : {'user': 'user2', 'password': 'pass',
208-
... 'port': 2223, 'private_key': 'my_other_key.pem'},
217+
... 'port': 2223,
218+
... 'private_key': load_private_key(open('my_other_key.pem'))},
209219
... }
210220
>>> hosts = host_config.keys()
211221
>>> client = ParallelSSHClient(hosts, host_config=host_config)

pssh/utils.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,22 @@ def enable_host_logger():
4747
as it becomes available"""
4848
enable_logger(host_logger)
4949

50-
def load_private_key(pkey):
50+
def load_private_key(_pkey):
5151
"""Load private key from pkey file object or filename
5252
5353
:param pkey: File object or file name containing private key
5454
:type pkey: file/str"""
55-
pkey = None
56-
if not isinstance(pkey, file):
57-
pkey = open(pkey)
55+
if not hasattr(_pkey, 'read'):
56+
_pkey = open(_pkey)
5857
for keytype in [RSAKey, DSSKey, ECDSAKey]:
5958
try:
60-
pkey = keytype.from_private_key(pkey)
59+
pkey = keytype.from_private_key(_pkey)
6160
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
61+
_pkey.seek(0)
62+
continue
63+
else:
64+
return pkey
65+
logger.error("Failed to load private key using all available key types - giving up..")
6666

6767
# def enable_pssh_logger():
6868
# """Enable parallel-ssh's logger to stdout"""

tests/test_client_private_key_dsa

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-----BEGIN DSA PRIVATE KEY-----
2+
MIIBvQIBAAKBgQDnUBhCcJYWWxhFkD8RgFLNGhW59FILG819HFPCqFKoOzJAHsSp
3+
NRqlNi+IVxrFitFAfs8W/CwpNCZ3Co5I9dQ1pfeWs1PSrP2r7v95kJJBXSWh3c79
4+
416p0lS+Al2Ii4uNOyH25p+8I4W6VEm4YD0vpQuY5B3o8z0gGlIMWF49qwIVAMo4
5+
XAaxdeiQS1sg5ySMKxY0LKkhAoGBAM9+7NvijLjw3IeJes20DWyHE4AZMLnRhT6v
6+
Eo8xsy4yv6dHHkm0uxhO8wvtY7vtjDLPGjGI8kbqY1npO9ZHq32BFbmKQW7Lett+
7+
X7EoL7U/F1VAbxv6BtRkGslf3E6JqyfaHoxhsS/PvX9dA7MK4Jev55/86o68ETO2
8+
1PAu1SumAoGBAIKKFRzjN0jhSaMZyY9tBcW+94RcAaQv/jfij0EVYo+5F30y84NM
9+
mH7JhYbQ5A3UiAHyQSnz7q7vkAZ8gXH9NjVSu8yyMIvsW71gK3IHTC6rFWu9l1e6
10+
XlMmqblX/KY6z+K2Xg3zQ3djqRBTlNycGFwkLoM8I6n1nUDqLo/GI2JrAhUApeOU
11+
f0nVYotZrqSiJ168QM1Spzk=
12+
-----END DSA PRIVATE KEY-----
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MHcCAQEEIMeaGUuH+Z+9BFhohwC+n08uzZMOOrdsdfUj5p4MIJGjoAoGCCqGSM49
3+
AwEHoUQDQgAE6x0VXeKfPMWkNfez96DZZNAPd7mYaetkl14rd9U4D4vqmwKCaQoZ
4+
Sar9yhqd2gS7/GiwVsxv7ICZnuZPBtVYJA==
5+
-----END EC PRIVATE KEY-----

tests/test_pssh_client.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import unittest
2323
from pssh import ParallelSSHClient, UnknownHostException, \
2424
AuthenticationException, ConnectionErrorException, SSHException, logger as pssh_logger
25+
from pssh.utils import load_private_key
2526
from embedded_server.embedded_server import start_server, make_socket, \
2627
logger as server_logger, paramiko_logger
2728
from embedded_server.fake_agent import FakeAgent
@@ -34,8 +35,8 @@
3435
import warnings
3536
import shutil
3637

37-
USER_KEY = paramiko.RSAKey.from_private_key_file(
38-
os.path.sep.join([os.path.dirname(__file__), 'test_client_private_key']))
38+
PKEY_FILENAME = os.path.sep.join([os.path.dirname(__file__), 'test_client_private_key'])
39+
USER_KEY = paramiko.RSAKey.from_private_key_file(PKEY_FILENAME)
3940

4041
server_logger.setLevel(logging.DEBUG)
4142
pssh_logger.setLevel(logging.DEBUG)
@@ -648,3 +649,44 @@ def test_escaped_quotes(self):
648649
self.assertEqual(expected, stdout,
649650
msg="Got unexpected output. Expected %s, got %s" % (
650651
expected, stdout,))
652+
653+
def test_host_config(self):
654+
"""Test per-host configuration functionality of ParallelSSHClient"""
655+
hosts = ['127.0.0.%01d' % n for n in xrange(1,3)]
656+
host_config = dict.fromkeys(hosts)
657+
servers = []
658+
user = 'overriden_user'
659+
password = 'overriden_pass'
660+
for host in hosts:
661+
_socket = make_socket(host)
662+
port = _socket.getsockname()[1]
663+
host_config[host] = {}
664+
host_config[host]['port'] = port
665+
host_config[host]['user'] = user
666+
host_config[host]['password'] = password
667+
server = start_server(_socket, fail_auth=hosts.index(host))
668+
servers.append((server, port))
669+
pkey_data = load_private_key(PKEY_FILENAME)
670+
host_config[hosts[0]]['private_key'] = pkey_data
671+
client = ParallelSSHClient(hosts, host_config=host_config)
672+
output = client.run_command(self.fake_cmd, stop_on_errors=False)
673+
client.join(output)
674+
for host in hosts:
675+
self.assertTrue(host in output)
676+
try:
677+
raise output[hosts[1]]['exception']
678+
except AuthenticationException, ex:
679+
pass
680+
else:
681+
raise AssertionError("Expected AutnenticationException on host %s",
682+
hosts[0])
683+
self.assertFalse(output[hosts[1]]['exit_code'],
684+
msg="Execution failed on host %s" % (hosts[1],))
685+
self.assertTrue(client.host_clients[hosts[0]].user == user,
686+
msg="Host config user override failed")
687+
self.assertTrue(client.host_clients[hosts[0]].password == password,
688+
msg="Host config password override failed")
689+
self.assertTrue(client.host_clients[hosts[0]].pkey == pkey_data,
690+
msg="Host config pkey override failed")
691+
for (server, _) in servers:
692+
server.kill()

tests/test_utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from pssh import utils
22
import unittest
3+
import os
4+
from cStringIO import StringIO
35

6+
PKEY_FILENAME = os.path.sep.join([os.path.dirname(__file__), 'test_client_private_key'])
7+
DSA_KEY_FILENAME = os.path.sep.join([os.path.dirname(__file__), 'test_client_private_key_dsa'])
8+
ECDSA_KEY_FILENAME = os.path.sep.join([os.path.dirname(__file__), 'test_client_private_key_ecdsa'])
49

510
class ParallelSSHUtilsTest(unittest.TestCase):
611

@@ -13,3 +18,13 @@ def test_enabling_host_logger(self):
1318
def test_enabling_pssh_logger(self):
1419
utils.enable_logger(utils.logger)
1520
self.assertTrue(len(utils.logger.handlers)==1)
21+
22+
23+
def test_loading_key_files(self):
24+
for key_filename in [PKEY_FILENAME, DSA_KEY_FILENAME, ECDSA_KEY_FILENAME]:
25+
pkey = utils.load_private_key(key_filename)
26+
self.assertTrue(pkey, msg="Error loading key from file %s" % (key_filename,))
27+
pkey = utils.load_private_key(open(key_filename))
28+
self.assertTrue(pkey, msg="Error loading key from open file object for file %s" % (key_filename,))
29+
fake_key = StringIO("blah blah fakey fakey key")
30+
self.assertFalse(utils.load_private_key(fake_key))

0 commit comments

Comments
 (0)