view silvermirror/unify.py @ 0:abb358e2434c

initial commit of silvermirror, from http://my-svn.assembla.com/svn/arbez/silvermirror
author k0s <k0scist@gmail.com>
date Mon, 07 Sep 2009 15:39:06 -0400
parents
children 9b139702a8f9
line wrap: on
line source

#!/usr/bin/env python

import getpass
import os
import pexpect
import socket
import subprocess
import sys

from martini.config import ConfigMunger
from optparse import OptionParser
from pprint import pprint
from utils import home
from utils import ip_addresses

def make_config(filename):
    # XXX needed?
    raise NotImplementedError('Need to specify a config file, like\n~/silvermirror/silvermirror.ini')

def read_config(filename):
    config = ConfigMunger(filename).dict()

    ### main configuration
    main = config.pop('::SilverMirror::', {})
    if not main.get('basedir'):
        main['basedir'] = home()
    main['ignore'] = main.get('ignore', '').split() # patterns to ignore - not used
    main['hosts'] = main.get('hosts', '').split()
    main['password'] = main.get('password', 'true') # XXX not used
    main['timeout'] = float(main.get('timeout', '10.'))

    ### resources
    for resource in config:

        # directory of resource
        directory = config[resource].get('directory', resource)
        if not os.path.isabs(directory):
            # XXX note: absolute directories will not work for now
            # XXX so....don't do this!
            directory = os.path.join(main['basedir'], directory)
        config[resource]['directory'] = directory.rstrip(os.path.sep)

        # per-resource files to ignore
        # XXX  regexps for now (see `man unison`)
        # - this is bad as whitespace patterns cannot be ignored
        ignore = main['ignore'][:]
        if config[resource].has_key('ignore'):
            ignore += config[resource]['ignore'].split()
        config[resource]['ignore'] = ignore

    ###
    config = { 'main': main, 'resources': config }
    return config

def unify(args=sys.argv[1:]):

    # passwords
    pw = {}

    ### command line options
    parser = OptionParser()
    parser.add_option('-c', '--config')
    parser.add_option('-H', '--host', dest='hosts',
                      action='append', default=None)
    parser.add_option('--no-password', dest='password',
                      action='store_false', default=True)
    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:
        assert os.path.exists(options.config)
        conf = read_config(options.config)
    else:
        for i in user_conf, '/etc/silvermirror':
            if os.path.exists(i):
                conf = read_config(i)
                break
        else:
            conf = make_config(user_conf)

    # 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

    ### 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)

if __name__ == '__main__':
    unify()