From 3ea0e9d3b7b157ab0aa4faaaa49c93a03285eff4 Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Wed, 23 Sep 2009 14:12:38 +0200 Subject: [PATCH] Added support for sftp (based on paramiko) --- git-ftp-sync | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/git-ftp-sync b/git-ftp-sync index 7d3528b..af32cbd 100755 --- a/git-ftp-sync +++ b/git-ftp-sync @@ -26,6 +26,7 @@ from subprocess import Popen, PIPE, call import re import os from os.path import dirname, normpath +from urlparse import urlparse try: defpass = os.environ["PASSWORD"] @@ -34,26 +35,28 @@ except KeyError: defpass = "" opt_parser = OptionParser(usage="usage: %prog [options] repository revision") -opt_parser.add_option("-H", "--host", dest="host", - help="FTP server address. If not specified update files localy without FTP.") -opt_parser.add_option("-u", "--user", dest="user", - help="FTP login name") +opt_parser.add_option("-U", "--url", dest="url", default="", + help="Destination URL (available protocols: sftp, ftp, file). Depricates -H, -u and -d.") opt_parser.add_option("-p", "--pass", dest="password", default=defpass, help="FTP password (defaults to environment variable PASSWORD)") opt_parser.add_option("-r", "--repodir", dest="repodir", default="", help="Synchronize only this directory (and its subdirectories) from within a repository") +opt_parser.add_option("-H", "--host", dest="host", + help="FTP server address. If not specified update files localy without FTP.") +opt_parser.add_option("-u", "--user", dest="user", + help="FTP login name") opt_parser.add_option("-d", "--dir", dest="dir", default="", help="An existing directory (on FTP site or localhost), where to store synchronized files.") class RepoChange: """Represents one line in git diff --name-status""" - def __init__(self, type, path, oldrev, newrev, options): + def __init__(self, type, path, oldrev, newrev, options, destroot=None): self.type = type.strip() # one of ADMRCUX self.repo_path = path if path.startswith(options.repodir): # www/something -> something path = path[len(options.repodir):] - if options.dir: # something -> prefix/something - path = options.dir+"/"+path + if destroot: # something -> prefix/something + path = destroot+"/"+path self.dest_path = normpath(path) self.oldrev = oldrev self.newrev = newrev @@ -156,6 +159,65 @@ class FTPSync(Syncer): except ftplib.error_perm, detail: perror("FTP warning: %s %s" % (detail, path)) +class SFTPSync(Syncer): + def __init__(self, url): + import paramiko + # get host key, if we know one + hostkeytype = None + hostkey = None + try: + host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts')) + except IOError: + try: + # try ~/ssh/ too, because windows can't have a folder named ~/.ssh/ + host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts')) + except IOError: + print '*** Unable to open host keys file' + host_keys = {} + + if host_keys.has_key(url.hostname): + hostkeytype = host_keys[url.hostname].keys()[0] + hostkey = host_keys[url.hostname][hostkeytype] + print 'Using host key of type %s' % hostkeytype + + + # now, connect and use paramiko Transport to negotiate SSH2 across the connection + port = 22 + if url.port: port = url.port + self.t = paramiko.Transport((url.hostname, port)) + password = url.password + if not password: password = options.password + self.t.connect(username=url.username, password=password, hostkey=hostkey) + self.sftp = paramiko.SFTPClient.from_transport(self.t) + + def close(self): + self.sftp.close() + self.t.close() + + def _mkd(self, path): + try: + self.sftp.mkdir(path) + except IOError, detail: + print "sftp warning:", detail + + def _storbinary(self, file, path): + remote = self.sftp.open(path, 'w') + s = file.read(10000) + while s: + remote.write(s) + s = file.read(10000) + remote.close() + + + def _rmd(self, path): + """ Delete everything reachable from the directory named in 'self.dest_path', + assuming there are no symbolic links.""" + self.sftp.rmdir(path) + # FIXME: this should be recursive deletion + + def _delete(self, path): + self.sftp.remove(path) + class LocalSync(Syncer): def _mkd(self, path): @@ -258,6 +320,12 @@ def selftest(): ret = os.system(commands % hook) if ret: return + print "\n\nChecking SFTP sync (server: localhost, user: test, password: env. variable PASSWORD)" + password = defpass + hook = """#!/bin/sh\nexport PASSWORD=%(password)s\n%(selfpath)s -r www --url sftp://test@localhost""" % locals() + ret = os.system(commands % hook) + if ret: return + print print "Selftest succeeded!" @@ -270,7 +338,7 @@ def update_ref(hash): #print "Runnging", cmd os.system(cmd) -def add_to_change_list(changes, git_command, oldrev, newrev): +def add_to_change_list(changes, git_command, oldrev, newrev, destroot): # Read changes ##print "Running: ", git_command gitdiff = Popen(git_command, @@ -280,7 +348,7 @@ def add_to_change_list(changes, git_command, oldrev, newrev): ##print line, m = change_re.match(line) if (m): - change = RepoChange(m.group(1), m.group(2), oldrev, newrev, options) + change = RepoChange(m.group(1), m.group(2), oldrev, newrev, options, destroot) if change.inDir(options.repodir): changes.append(change) @@ -294,6 +362,13 @@ if len(args) > 0 and args[0] == "selftest": options.repodir = normpath(options.repodir).lstrip(".") if options.repodir: options.repodir+="/" +if options.host: + url = urlparse("ftp://"+options.user+"@"+options.host+"/"+options.dir) +elif options.dir and not options.url: + url = urlparse("file:///"+options.dir) +else: + url = urlparse(options.url) + changes = list() # Read the changes @@ -311,7 +386,7 @@ if 'GIT_DIR' in os.environ: oldrev = None git_command = r"git ls-tree -r --name-only %s | sed -e 's/\(.*\)/A \1/'"%(newrev); - add_to_change_list(changes, git_command, oldrev, newrev) + add_to_change_list(changes, git_command, oldrev, newrev, url.path) else: # Manual invocation newrev = "HEAD" @@ -325,10 +400,12 @@ if not changes: # Apply changes try: - if options.host: + if url.scheme == "ftp": syncer = FTPSync(FTP(options.host, options.user, options.password)) - else: + elif url.scheme == "file": syncer = LocalSync() + elif url.scheme == "sftp": + syncer = SFTPSync(url) for change in changes: syncer.sync(change, options.dir) @@ -336,10 +413,12 @@ try: syncer.close() -except ftplib.all_errors, detail: - perror("FTP synchronization error: %s" % detail); - perror("I will try it next time again"); - sys.exit(1) +# except ftplib.all_errors, detail: +# perror("FTP synchronization error: %s" % detail); +# perror("I will try it next time again"); +# sys.exit(1) +except: + raise # If succeessfull, update remote ref update_ref(newrev) -- 2.11.4.GIT