From c22df4490027a62268f1d46073d9513aad83ef2a Mon Sep 17 00:00:00 2001 From: Robey Pointer Date: Tue, 27 Jun 2006 21:59:19 -0700 Subject: [PATCH] [project @ robey@lag.net-20060628045919-ffac82c51c51b3df] make HostKeys use odict to ensure order is preserved, and add HostKeys.save() --- paramiko/hostkeys.py | 27 ++++++++++++++++--- paramiko/odict.py | 63 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 paramiko/odict.py diff --git a/paramiko/hostkeys.py b/paramiko/hostkeys.py index 5d88084..7cc8d40 100644 --- a/paramiko/hostkeys.py +++ b/paramiko/hostkeys.py @@ -26,6 +26,7 @@ import UserDict from paramiko.common import * from paramiko.dsskey import DSSKey +from paramiko.odict import odict from paramiko.rsakey import RSAKey @@ -50,7 +51,7 @@ class HostKeys (UserDict.DictMixin): @type filename: str """ # hostname -> keytype -> PKey - self._keys = {} + self._keys = odict() self.contains_hashes = False if filename is not None: self.load(filename) @@ -68,7 +69,7 @@ class HostKeys (UserDict.DictMixin): @type key: L{PKey} """ if not hostname in self._keys: - self._keys[hostname] = {} + self._keys[hostname] = odict() if hostname.startswith('|1|'): self.contains_hashes = True self._keys[hostname][keytype] = key @@ -89,7 +90,7 @@ class HostKeys (UserDict.DictMixin): @raise IOError: if there was an error reading the file """ - f = file(filename, 'r') + f = open(filename, 'r') for line in f: line = line.strip() if (len(line) == 0) or (line[0] == '#'): @@ -105,6 +106,26 @@ class HostKeys (UserDict.DictMixin): elif keytype == 'ssh-dss': self.add(host, keytype, DSSKey(data=base64.decodestring(key))) f.close() + + def save(self, filename): + """ + Save host keys into a file, in the format used by openssh. The order of + keys in the file will be preserved when possible (if these keys were + loaded from a file originally). The single exception is that combined + lines will be split into individual key lines, which is arguably a bug. + + @param filename: name of the file to write + @type filename: str + + @raise IOError: if there was an error writing the file + + @since: 1.6.1 + """ + f = open(filename, 'w') + for hostname, d in self._keys.iteritems(): + for keytype, key in d.iteritems(): + f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) + f.close() def lookup(self, hostname): """ diff --git a/paramiko/odict.py b/paramiko/odict.py new file mode 100644 index 0000000..103e52f --- /dev/null +++ b/paramiko/odict.py @@ -0,0 +1,63 @@ +# +# This file and source code are in the public domain. +# + +class odict (dict): + """ + A dictionary with ordered keys. Based on the cookbook recipe at: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747 + """ + + def __init__(self, *larg, **kwarg): + self._keys = [] + dict.__init__(self, *larg, **kwarg) + + def __delitem__(self, key): + dict.__delitem__(self, key) + self._keys.remove(key) + + def __setitem__(self, key, item): + dict.__setitem__(self, key, item) + if key not in self._keys: + self._keys.append(key) + + def clear(self): + dict.clear(self) + self._keys = [] + + def copy(self): + od = odict(self) + return od + + def items(self): + return zip(self._keys, self.values()) + + def iteritems(self): + for k in self._keys: + yield k, self[k] + + def keys(self): + return self._keys[:] + + def popitem(self): + try: + key = self._keys[-1] + except IndexError: + raise KeyError('dictionary is empty') + + val = self[key] + del self[key] + + return (key, val) + + def setdefault(self, key, failobj=None): + if key not in self._keys: + self._keys.append(key) + dict.setdefault(self, key, failobj) + + def update(self, d): + for key, item in d.items(): + self.__setitem__(key, item) + + def values(self): + return map(self.get, self._keys)