comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:abb358e2434c
1 #!/usr/bin/env python
2
3 import getpass
4 import os
5 import pexpect
6 import socket
7 import subprocess
8 import sys
9
10 from martini.config import ConfigMunger
11 from optparse import OptionParser
12 from pprint import pprint
13 from utils import home
14 from utils import ip_addresses
15
16 def make_config(filename):
17 # XXX needed?
18 raise NotImplementedError('Need to specify a config file, like\n~/silvermirror/silvermirror.ini')
19
20 def read_config(filename):
21 config = ConfigMunger(filename).dict()
22
23 ### main configuration
24 main = config.pop('::SilverMirror::', {})
25 if not main.get('basedir'):
26 main['basedir'] = home()
27 main['ignore'] = main.get('ignore', '').split() # patterns to ignore - not used
28 main['hosts'] = main.get('hosts', '').split()
29 main['password'] = main.get('password', 'true') # XXX not used
30 main['timeout'] = float(main.get('timeout', '10.'))
31
32 ### resources
33 for resource in config:
34
35 # directory of resource
36 directory = config[resource].get('directory', resource)
37 if not os.path.isabs(directory):
38 # XXX note: absolute directories will not work for now
39 # XXX so....don't do this!
40 directory = os.path.join(main['basedir'], directory)
41 config[resource]['directory'] = directory.rstrip(os.path.sep)
42
43 # per-resource files to ignore
44 # XXX regexps for now (see `man unison`)
45 # - this is bad as whitespace patterns cannot be ignored
46 ignore = main['ignore'][:]
47 if config[resource].has_key('ignore'):
48 ignore += config[resource]['ignore'].split()
49 config[resource]['ignore'] = ignore
50
51 ###
52 config = { 'main': main, 'resources': config }
53 return config
54
55 def unify(args=sys.argv[1:]):
56
57 # passwords
58 pw = {}
59
60 ### command line options
61 parser = OptionParser()
62 parser.add_option('-c', '--config')
63 parser.add_option('-H', '--host', dest='hosts',
64 action='append', default=None)
65 parser.add_option('--no-password', dest='password',
66 action='store_false', default=True)
67 parser.add_option('--test', dest='test',
68 action='store_true', default=False)
69 (options, args) = parser.parse_args()
70
71 ### configuration
72 user_conf = os.path.join(home(), '.silvermirror')
73 if options.config:
74 assert os.path.exists(options.config)
75 conf = read_config(options.config)
76 else:
77 for i in user_conf, '/etc/silvermirror':
78 if os.path.exists(i):
79 conf = read_config(i)
80 break
81 else:
82 conf = make_config(user_conf)
83
84 # XXX needed for now
85 assert conf['main']['basedir'] == home()
86
87 ### determine hosts to sync with
88 hosts = set(options.hosts or conf['main']['hosts'])
89 addresses = ip_addresses().values()
90 hosts = hosts.difference(addresses) # don't sync with self
91 _hosts = []
92 for host in hosts:
93 s = socket.socket()
94 s.settimeout(conf['main']['timeout'])
95 if options.test:
96 print 'Resolving %s' % host
97 try:
98 s.connect((host, 22))
99 s.close()
100 except (socket.gaierror, socket.timeout, socket.error):
101 continue
102 _hosts.append(host)
103 hosts = _hosts
104 if options.test:
105 print 'Hosts:'
106 for host in hosts:
107 print host
108 assert hosts
109
110 if options.password and conf['main']['password']:
111 for host in hosts:
112 pw[host] = getpass.getpass('Enter password for %s: ' % host)
113 # TODO: ensure that the hosts are resolvable
114 # XXX: hosts should actually be manageable on a per-resource basis
115
116 ### determine resources to sync
117 cwd = os.path.realpath(os.getcwd())
118 resources = conf['resources']
119 _resources = args
120 if 'all' not in _resources:
121 if _resources:
122 resources = dict([(key, value) for key, value in resources.items()
123 if key in _resources])
124 else:
125 for key, value in resources.items():
126 directory = os.path.realpath(value['directory']) + os.sep
127 if (cwd + os.sep).startswith(directory):
128 resources = { key: value }
129 break
130 if options.test:
131 print 'Resources:'
132 pprint(resources)
133
134 ### sync with hosts
135 os.chdir(conf['main']['basedir'])
136 for resource in resources:
137 for host in hosts:
138 command = ['unison', '-auto', '-batch', resource, 'ssh://%s/%s' % (host, resource)]
139
140 # XXX - to refactor?
141 for i in resources[resource]['ignore']:
142 command.extend(('-ignore', "'Name %s'" % i))
143
144 command = ' '.join(command)
145 print command # XXX debug
146 if not options.test:
147 child = pexpect.spawn(command, timeout=36000, maxread=1)
148 child.expect('password: ')
149 child.sendline(pw[host])
150 print child.read()
151 # subprocess.call(command)
152 os.chdir(cwd)
153
154 if __name__ == '__main__':
155 unify()