Mercurial > hg > silvermirror
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() |