Commits

Rohan de Swardt committed 493ec78

SSH storage backend for file replication

Comments (0)

Files changed (1)

storages/backends/ssh.py

+from django.core.files.storage import FileSystemStorage
+
+import os
+import paramiko
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+
+
+class SSHStorageException(Exception): pass
+
+class SSHStorage(FileSystemStorage):
+
+    def get_available_name(self, name):
+        """Overwrite file if it exists"""
+        return name
+
+
+    def _save(self, name, content):
+        """After local filesystem save copy file to remote hosts"""
+        name = super(SSHStorage, self)._save(name, content)
+        self.scp_file(name)
+        return name
+
+
+    def scp_file(self, path):
+        """Copy a local file to other machines via ssh"""
+        ssh = paramiko.SSHClient()
+        ssh.load_host_keys(os.path.expanduser(os.path.join("~", ".ssh", "known_hosts")))
+        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+
+        try:
+            slaves = settings.FILE_REPLICATION_SLAVES
+        except Exception, e:
+            raise ImproperlyConfigured('No SSHStorage hosts configured')
+
+        for s in slaves:
+            try:
+                ssh.connect(s['host'], username=s['username'],password=s['password'])
+            except Exception, e:
+                raise SSHStorageException("error connecting to host [%s]" % s['host'])
+
+            sftp = ssh.open_sftp()
+            local_path = os.path.join(self.location, path)
+
+            if 'MEDIA_ROOT' in s:
+                remote_path = os.path.join(s['MEDIA_ROOT'], path)
+            else:
+                remote_path = local_path
+
+            remote_dir = os.path.dirname(remote_path)
+            self._mk_remote_dirs(sftp, remote_dir)
+
+            try:
+                sftp.put(local_path, remote_path)
+            except Exception, e:
+                raise SSHStorageException("error copying file [%s] to host [%s] - %s" \
+                        % (remote_path, s['host'], e.strerror))
+
+            sftp.close()
+            ssh.close()
+
+
+    def _remote_exists(self, sftp, path):
+        """
+        os.path.exists for paramiko's SCP object
+        """
+        try:
+            sftp.stat(path)
+        except:
+            return False
+        else:
+            return True
+
+
+    def _mk_remote_dirs(self, sftp, path):
+        if self._remote_exists(sftp, path): return
+        parts = path.split('/')
+        for x in range(0, len(parts)):
+            slice = x+1
+            path_part = '/'.join(parts[:slice])
+            if not path_part: continue
+            if not self._remote_exists(sftp, path_part):
+                sftp.mkdir(path_part)
+
+
+            
+