Only load SSH host keys once, rather than for each host.
Loading a large known hosts file is quite slow (.5s) and when doing this
over many target hosts this becomes very obvious. There's no need to do
this so just use the same `HostKeys` object for all SSH clients.
@@ -16,6 +16,7 @@ from paramiko import (
DSSKey,
ECDSAKey,
Ed25519Key,
+ HostKeys,
MissingHostKeyPolicy,
PasswordRequiredException,
RSAKey,
@@ -41,6 +42,7 @@ from .util import (
)
EXECUTION_CONNECTOR = True
+SYSTEM_HOST_KEYS = HostKeys()
class WarningPolicy(MissingHostKeyPolicy):
@@ -194,12 +196,28 @@ def _make_paramiko_kwargs(state, host):
return kwargs
+@memoize # only load system host keys once
+def _load_system_host_keys():
+ logger.debug('Loading SSH host keys')
+
+ known_hosts_filename = path.expanduser('~/.ssh/known_hosts')
+ if path.exists(known_hosts_filename):
+ try:
+ SYSTEM_HOST_KEYS.load(known_hosts_filename)
+ # Unfortunately paramiko bails for any dodge line in known hosts
+ # See: https://github.com/Fizzadar/pyinfra/issues/683
+ except Exception as e:
+ logger.warning('Failed to load system host keys: {0}'.format(e))
+
+
def connect(state, host):
'''
Connect to a single host. Returns the SSH client if succesful. Stateless by
design so can be run in parallel.
'''
+ _load_system_host_keys()
+
kwargs = _make_paramiko_kwargs(state, host)
logger.debug('Connecting to: {0} ({1})'.format(host.name, kwargs))
@@ -209,13 +227,7 @@ def connect(state, host):
# Create new client & connect to the host
client = SSHClient()
client.set_missing_host_key_policy(WarningPolicy())
-
- try:
- client.load_system_host_keys()
- # Unfortunately paramiko bails for any dodge line in known hosts
- # See: https://github.com/Fizzadar/pyinfra/issues/683
- except Exception as e:
- logger.warning('Failed to load system host keys: {0}'.format(e))
+ client._system_host_keys = SYSTEM_HOST_KEYS
client.connect(hostname, **kwargs)
return client
