# HG changeset patch # User k0s # Date 1254022602 14400 # Node ID 9b139702a8f9e355e1f057af6c66ef3114d775dc # Parent abb358e2434c7ac7c1775a37c8dc2f57e9f60f40 use a real backend architecture with an inteface and vastly simplify unify.py diff -r abb358e2434c -r 9b139702a8f9 setup.py --- a/setup.py Mon Sep 07 15:39:06 2009 -0400 +++ b/setup.py Sat Sep 26 23:36:42 2009 -0400 @@ -27,6 +27,9 @@ entry_points=""" # -*- Entry points: -*- [console_scripts] - silvermirror = silvermirror.unify:unify + silvermirror = silvermirror.unify:main + + [silvermirror.reflectors] + unison = silvermirror.unison:unison """, ) diff -r abb358e2434c -r 9b139702a8f9 silvermirror-whitepaper.txt --- a/silvermirror-whitepaper.txt Mon Sep 07 15:39:06 2009 -0400 +++ b/silvermirror-whitepaper.txt Sat Sep 26 23:36:42 2009 -0400 @@ -42,6 +42,8 @@ * ignore: global patterns of files and directories to ignore. Paths matching these patterns will not be versioned. + * reflector: which back-end to use (unison, hg, etc) + Each section has the following configuration options: * directory: path of the resource. If a relative path is used, it is diff -r abb358e2434c -r 9b139702a8f9 silvermirror/hg.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/silvermirror/hg.py Sat Sep 26 23:36:42 2009 -0400 @@ -0,0 +1,19 @@ +#!/usr/bin/env python +""" +stub for the hg backend of silvermirror +""" + +import os +from mercurial import commands, hg, ui + +def update(host, path): + """ + get changes from host on path + """ + ui = ui.ui() + try: + repo = hg.repository(ui, path) + command = commands.pull + except mercurial.repo.RepoError: + repo = hg.repository(ui, 'ssh://%s/%s' % (host, path)) + command = commands.clone diff -r abb358e2434c -r 9b139702a8f9 silvermirror/interface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/silvermirror/interface.py Sat Sep 26 23:36:42 2009 -0400 @@ -0,0 +1,55 @@ +#!/usr/bin/env python +""" +interface for Reflector backends +""" + +def notimplemented(func): + """ + mark a function as not implemented + TODO: calling should raise a NotImplementedError + """ + func.notimplemented = True + return func + +class Reflector(object): + + def __init__(self): + pass + + ### API + + @notimplemented + def sync(self): + """ + synchronize + """ + +### for testing notimplemented decorator + +class Foo(object): + + @notimplemented + def bar(self): + "stufff" + + @notimplemented + def fleem(self): + pass + + @notimplemented + def foox(self): + pass + +class Bar(Foo): + + def bar(self): + return 1 + + @notimplemented + def fleem(self): + pass + +if __name__ == '__main__': + foo = Foo() + bar = Bar() + import pdb; pdb.set_trace() diff -r abb358e2434c -r 9b139702a8f9 silvermirror/unify.py --- a/silvermirror/unify.py Mon Sep 07 15:39:06 2009 -0400 +++ b/silvermirror/unify.py Sat Sep 26 23:36:42 2009 -0400 @@ -9,6 +9,7 @@ from martini.config import ConfigMunger from optparse import OptionParser +from pkg_resources import iter_entry_points from pprint import pprint from utils import home from utils import ip_addresses @@ -52,11 +53,73 @@ config = { 'main': main, 'resources': config } return config -def unify(args=sys.argv[1:]): +def unify(conf, _resources, test=False): # passwords pw = {} + # XXX needed for now + assert conf['main']['basedir'] == home() + + ### determine hosts to sync with + hosts = conf['hosts'] + addresses = ip_addresses().values() + hosts = hosts.difference(addresses) # don't sync with self + _hosts = [] + for host in hosts: + s = socket.socket() + s.settimeout(conf['main']['timeout']) + if test: + print 'Resolving %s' % host + try: + s.connect((host, 22)) + s.close() + except (socket.gaierror, socket.timeout, socket.error): + continue + _hosts.append(host) + hosts = _hosts + if test: + print 'Hosts:' + for host in hosts: + print host + assert hosts + + if conf['main']['password']: + for host in hosts: + pw[host] = getpass.getpass('Enter password for %s: ' % host) + # TODO: ensure that the hosts are resolvable + # XXX: hosts should actually be manageable on a per-resource basis + + ### determine resources to sync + cwd = os.path.realpath(os.getcwd()) + resources = conf['resources'] + if 'all' not in _resources: + if _resources: + resources = dict([(key, value) for key, value in resources.items() + if key in _resources]) + else: + for key, value in resources.items(): + directory = os.path.realpath(value['directory']) + os.sep + if (cwd + os.sep).startswith(directory): + resources = { key: value } + break + if test: + print 'Resources:' + pprint(resources) + + ### choose reflector backend + reflectors = dict([(i.name, i.load()) for i in iter_entry_points('silvermirror.reflectors')]) + reflector = reflectors['unison']() # only one right now + + ### sync with hosts + os.chdir(conf['main']['basedir']) + for resource in resources: + for host in hosts: + reflector.sync(host, resource, resources[resource]['ignore'], pw, test) + os.chdir(cwd) + +def main(args=sys.argv[1:]): + ### command line options parser = OptionParser() parser.add_option('-c', '--config') @@ -67,7 +130,8 @@ parser.add_option('--test', dest='test', action='store_true', default=False) (options, args) = parser.parse_args() - + + ### configuration user_conf = os.path.join(home(), '.silvermirror') if options.config: @@ -84,72 +148,11 @@ # XXX needed for now assert conf['main']['basedir'] == home() - ### determine hosts to sync with - hosts = set(options.hosts or conf['main']['hosts']) - addresses = ip_addresses().values() - hosts = hosts.difference(addresses) # don't sync with self - _hosts = [] - for host in hosts: - s = socket.socket() - s.settimeout(conf['main']['timeout']) - if options.test: - print 'Resolving %s' % host - try: - s.connect((host, 22)) - s.close() - except (socket.gaierror, socket.timeout, socket.error): - continue - _hosts.append(host) - hosts = _hosts - if options.test: - print 'Hosts:' - for host in hosts: - print host - assert hosts - - if options.password and conf['main']['password']: - for host in hosts: - pw[host] = getpass.getpass('Enter password for %s: ' % host) - # TODO: ensure that the hosts are resolvable - # XXX: hosts should actually be manageable on a per-resource basis + # fix up configuration from command line options + conf['hosts'] = set(options.hosts or conf['main']['hosts']) + conf['main']['password'] = options.password and conf['main']['password'] - ### determine resources to sync - cwd = os.path.realpath(os.getcwd()) - resources = conf['resources'] - _resources = args - if 'all' not in _resources: - if _resources: - resources = dict([(key, value) for key, value in resources.items() - if key in _resources]) - else: - for key, value in resources.items(): - directory = os.path.realpath(value['directory']) + os.sep - if (cwd + os.sep).startswith(directory): - resources = { key: value } - break - if options.test: - print 'Resources:' - pprint(resources) - - ### sync with hosts - os.chdir(conf['main']['basedir']) - for resource in resources: - for host in hosts: - command = ['unison', '-auto', '-batch', resource, 'ssh://%s/%s' % (host, resource)] - - # XXX - to refactor? - for i in resources[resource]['ignore']: - command.extend(('-ignore', "'Name %s'" % i)) - - command = ' '.join(command) - print command # XXX debug - if not options.test: - child = pexpect.spawn(command, timeout=36000, maxread=1) - child.expect('password: ') - child.sendline(pw[host]) - print child.read() - # subprocess.call(command) - os.chdir(cwd) + unify(conf, args, options.test) if __name__ == '__main__': unify() diff -r abb358e2434c -r 9b139702a8f9 silvermirror/unison.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/silvermirror/unison.py Sat Sep 26 23:36:42 2009 -0400 @@ -0,0 +1,23 @@ +""" +unison backend for silvermirror +""" + +import pexpect + +from interface import Reflector + +class unison(Reflector): + + def sync(self, host, resource, ignore=(), password=None, test=False): + command = ['unison', '-auto', '-batch', resource, 'ssh://%s/%s' % (host, resource)] + for i in ignore: + command.extend(('-ignore', "'Name %s'" % i)) + + command = ' '.join(command) + print command # XXX debug -- should go to logging + if not test: + child = pexpect.spawn(command, timeout=36000, maxread=1) + child.expect('password: ') + child.sendline(password[host]) + print child.read() # XXX -> logging +