1 # Cumulus: Efficient Filesystem Backup to the Cloud
2 # Copyright (C) 2010 The Cumulus Developers
3 # See the AUTHORS file for a list of contributors.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #needed for python 2.5
20 from __future__ import with_statement
22 from paramiko import Transport, SFTPClient, RSAKey, DSSKey
23 from paramiko.config import SSHConfig
25 from cumulus.store import Store, type_patterns, NotFoundError
32 class SSHHostConfig(dict):
33 def __init__(self, hostname, user = None, filename = None):
36 filename = os.path.expanduser('~/.ssh/config')
39 ssh_config = SSHConfig()
40 with open(filename) as config_file:
41 ssh_config.parse(config_file)
43 self.update(ssh_config.lookup(hostname))
45 self.defaults={'port': 22, 'user': getpass.getuser(), 'hostname': hostname, 'hostkeyalias': hostname}
50 def __getitem__(self, key):
52 return dict.__getitem__(self,key)
53 elif key == 'hostkeyalias' and 'hostname' in self:
54 return dict.__getitem__(self,'hostname')
56 return self.defaults[key]
59 class SFTPStore(Store):
60 """implements the sftp:// storage backend
62 configuration via openssh/sftp style urls and
65 does not support password authentication or password
66 protected authentication keys"""
67 def __init__(self, url, **kw):
68 if self.netloc.find('@') != -1:
69 user, self.netloc = self.netloc.split('@')
73 self.config = SSHHostConfig(self.netloc, user)
75 host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
77 self.hostkey = host_keys[self.config['hostkeyalias']].values()[0]
79 print str(self.config)
83 if(self.config.has_key('identityfile')):
84 key_file = os.path.expanduser(self.config['identityfile'])
85 #not really nice but i don't see a cleaner way atm...
87 self.auth_key = RSAKey (key_file)
88 except SSHException, e:
89 if e.message == 'Unable to parse file':
90 self.auth_key = DSAKey (key_file)
94 filename = os.path.expanduser('~/.ssh/id_rsa')
95 if os.path.exists(filename):
96 self.auth_key = RSAKey(filename)
98 filename = os.path.expanduser('~/.ssh/id_dsa')
99 if (os.path.exists(filename)):
100 self.auth_key = DSSKey (filename)
105 self.t = Transport((self.config['hostname'], self.config['port']))
106 self.t.connect(username = self.config['user'], pkey = self.auth_key)
107 self.client = SFTPClient.from_transport(self.t)
108 self.client.chdir(self.path)
110 def __build_fn(self, name):
111 return "%s/%s" % (self.path, name)
113 def list(self, type):
114 return filter(type_patterns[type].match, self.client.listdir(self.path))
116 def get(self, type, name):
117 return self.client.open(self.__build_fn(name), mode = 'rb')
119 def put(self, type, name, fp):
120 remote_file = self.client.open(self.__build_fn(name), mode = 'wb')
122 while (len(buf) > 0):
123 remote_file.write(buf)
127 def delete(self, type, name):
128 self.client.remove(self.__build_fn(name))
130 def stat(self, type, name):
132 stat = self.client.stat(self.__build_fn(name))
133 return {'size': stat.st_size}
138 """connection has to be explicitly closed, otherwise
139 it will hold the process running idefinitly"""