1 """%%prog %s [options] %s |
1 """the cubicweb-ctl tool, based on logilab.common.clcommands to |
2 |
2 provide a pluggable commands system. |
3 CubicWeb main instances controller. |
3 |
|
4 |
|
5 :organization: Logilab |
|
6 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
|
7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
4 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
5 %s""" |
9 """ |
6 |
10 __docformat__ = "restructuredtext en" |
|
11 |
|
12 # *ctl module should limit the number of import to be imported as quickly as |
|
13 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash |
|
14 # completion). So import locally in command helpers. |
7 import sys |
15 import sys |
8 from os import remove, listdir, system, pathsep |
16 from os import remove, listdir, system, pathsep |
9 try: |
17 try: |
10 from os import kill, getpgid |
18 from os import kill, getpgid |
11 except ImportError: |
19 except ImportError: |
12 def kill(*args): pass |
20 def kill(*args): |
13 def getpgid(): pass |
21 """win32 kill implementation""" |
|
22 def getpgid(): |
|
23 """win32 getpgid implementation""" |
14 |
24 |
15 from os.path import exists, join, isfile, isdir, dirname, abspath |
25 from os.path import exists, join, isfile, isdir, dirname, abspath |
16 |
26 |
17 from logilab.common.clcommands import register_commands, pop_arg |
27 from logilab.common.clcommands import register_commands, pop_arg |
18 from logilab.common.shellutils import ASK |
28 from logilab.common.shellutils import ASK |
19 |
29 |
20 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title |
30 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage |
21 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS |
31 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS |
22 from cubicweb.toolsutils import Command, main_run, rm, create_dir |
32 from cubicweb.toolsutils import Command, main_run, rm, create_dir, underline_title |
23 |
33 |
24 def wait_process_end(pid, maxtry=10, waittime=1): |
34 def wait_process_end(pid, maxtry=10, waittime=1): |
25 """wait for a process to actually die""" |
35 """wait for a process to actually die""" |
26 import signal |
36 import signal |
27 from time import sleep |
37 from time import sleep |
156 self.run_arg(appid) |
166 self.run_arg(appid) |
157 |
167 |
158 |
168 |
159 # base commands ############################################################### |
169 # base commands ############################################################### |
160 |
170 |
|
171 def version_strictly_lower(a, b): |
|
172 from logilab.common.changelog import Version |
|
173 if a: |
|
174 a = Version(a) |
|
175 if b: |
|
176 b = Version(b) |
|
177 return a < b |
|
178 |
|
179 def max_version(a, b): |
|
180 from logilab.common.changelog import Version |
|
181 return str(max(Version(a), Version(b))) |
|
182 |
|
183 class ConfigurationProblem(object): |
|
184 """Each cube has its own list of dependencies on other cubes/versions. |
|
185 |
|
186 The ConfigurationProblem is used to record the loaded cubes, then to detect |
|
187 inconsistencies in their dependencies. |
|
188 |
|
189 See configuration management on wikipedia for litterature. |
|
190 """ |
|
191 |
|
192 def __init__(self): |
|
193 self.cubes = {} |
|
194 |
|
195 def add_cube(self, name, info): |
|
196 self.cubes[name] = info |
|
197 |
|
198 def solve(self): |
|
199 self.warnings = [] |
|
200 self.errors = [] |
|
201 self.read_constraints() |
|
202 for cube, versions in sorted(self.constraints.items()): |
|
203 oper, version = None, None |
|
204 # simplify constraints |
|
205 if versions: |
|
206 for constraint in versions: |
|
207 op, ver = constraint |
|
208 if oper is None: |
|
209 oper = op |
|
210 version = ver |
|
211 elif op == '>=' and oper == '>=': |
|
212 version = max_version(ver, version) |
|
213 else: |
|
214 print 'unable to handle this case', oper, version, op, ver |
|
215 # "solve" constraint satisfaction problem |
|
216 if cube not in self.cubes: |
|
217 self.errors.append( ('add', cube, version) ) |
|
218 elif versions: |
|
219 lower_strict = version_strictly_lower(self.cubes[cube].version, version) |
|
220 if oper in ('>=','='): |
|
221 if lower_strict: |
|
222 self.errors.append( ('update', cube, version) ) |
|
223 else: |
|
224 print 'unknown operator', oper |
|
225 |
|
226 def read_constraints(self): |
|
227 self.constraints = {} |
|
228 self.reverse_constraints = {} |
|
229 for cube, info in self.cubes.items(): |
|
230 if hasattr(info,'__depends_cubes__'): |
|
231 use = info.__depends_cubes__ |
|
232 if not isinstance(use, dict): |
|
233 use = dict((key, None) for key in use) |
|
234 self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list') |
|
235 else: |
|
236 self.warnings.append('cube %s should define __depends_cubes__' % cube) |
|
237 use = dict((key, None) for key in info.__use__) |
|
238 for name, constraint in use.items(): |
|
239 self.constraints.setdefault(name,set()) |
|
240 if constraint: |
|
241 try: |
|
242 oper, version = constraint.split() |
|
243 self.constraints[name].add( (oper, version) ) |
|
244 except: |
|
245 self.warnings.append('cube %s depends on %s but constraint badly formatted: %s' |
|
246 % (cube, name, constraint)) |
|
247 self.reverse_constraints.setdefault(name, set()).add(cube) |
|
248 |
161 class ListCommand(Command): |
249 class ListCommand(Command): |
162 """List configurations, cubes and instances. |
250 """List configurations, cubes and instances. |
163 |
251 |
164 list available configurations, installed cubes, and registered instances |
252 list available configurations, installed cubes, and registered instances |
165 """ |
253 """ |
198 if cube in ('CVS', '.svn', 'shared', '.hg'): |
287 if cube in ('CVS', '.svn', 'shared', '.hg'): |
199 continue |
288 continue |
200 try: |
289 try: |
201 tinfo = cwcfg.cube_pkginfo(cube) |
290 tinfo = cwcfg.cube_pkginfo(cube) |
202 tversion = tinfo.version |
291 tversion = tinfo.version |
|
292 cfgpb.add_cube(cube, tinfo) |
203 except ConfigurationError: |
293 except ConfigurationError: |
204 tinfo = None |
294 tinfo = None |
205 tversion = '[missing cube information]' |
295 tversion = '[missing cube information]' |
206 print '* %s %s' % (cube.ljust(namesize), tversion) |
296 print '* %s %s' % (cube.ljust(namesize), tversion) |
207 if self.config.verbose: |
297 if self.config.verbose: |
233 print ' (BROKEN instance, %s)' % exc |
323 print ' (BROKEN instance, %s)' % exc |
234 continue |
324 continue |
235 else: |
325 else: |
236 print 'No instance available in %s' % regdir |
326 print 'No instance available in %s' % regdir |
237 print |
327 print |
238 |
328 # configuration management problem solving |
|
329 cfgpb.solve() |
|
330 if cfgpb.warnings: |
|
331 print 'Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings) |
|
332 if cfgpb.errors: |
|
333 print 'Errors:' |
|
334 for op, cube, version in cfgpb.errors: |
|
335 if op == 'add': |
|
336 print '* cube', cube, |
|
337 if version: |
|
338 print ' version', version, |
|
339 print 'is not installed, but required by %s' % ' '.join(cfgpb.reverse_constraints[cube]) |
|
340 else: |
|
341 print '* cube %s version %s is installed, but version %s is required by (%s)' % ( |
|
342 cube, cfgpb.cubes[cube].version, version, ', '.join(cfgpb.reverse_constraints[cube])) |
239 |
343 |
240 class CreateInstanceCommand(Command): |
344 class CreateInstanceCommand(Command): |
241 """Create an instance from a cube. This is an unified |
345 """Create an instance from a cube. This is an unified |
242 command which can handle web / server / all-in-one installation |
346 command which can handle web / server / all-in-one installation |
243 according to available parts of the software library and of the |
347 according to available parts of the software library and of the |
295 print ', '.join(cwcfg.available_cubes()) |
399 print ', '.join(cwcfg.available_cubes()) |
296 return |
400 return |
297 # create the registry directory for this instance |
401 # create the registry directory for this instance |
298 print '\n'+underline_title('Creating the instance %s' % appid) |
402 print '\n'+underline_title('Creating the instance %s' % appid) |
299 create_dir(config.apphome) |
403 create_dir(config.apphome) |
300 # load site_cubicweb from the cubes dir (if any) |
|
301 config.load_site_cubicweb() |
|
302 # cubicweb-ctl configuration |
404 # cubicweb-ctl configuration |
303 print '\n'+underline_title('Configuring the instance (%s.conf)' % configname) |
405 print '\n'+underline_title('Configuring the instance (%s.conf)' % configname) |
304 config.input_config('main', self.config.config_level) |
406 config.input_config('main', self.config.config_level) |
305 # configuration'specific stuff |
407 # configuration'specific stuff |
306 print |
408 print |
307 helper.bootstrap(cubes, self.config.config_level) |
409 helper.bootstrap(cubes, self.config.config_level) |
|
410 # input for cubes specific options |
|
411 for section in set(sect.lower() for sect, opt, optdict in config.all_options() |
|
412 if optdict.get('inputlevel') <= self.config.config_level): |
|
413 if section not in ('main', 'email', 'pyro'): |
|
414 print '\n' + underline_title('%s options' % section) |
|
415 config.input_config(section, self.config.config_level) |
308 # write down configuration |
416 # write down configuration |
309 config.save() |
417 config.save() |
310 self._handle_win32(config, appid) |
418 self._handle_win32(config, appid) |
311 print '-> generated %s' % config.main_config_file() |
419 print '-> generated %s' % config.main_config_file() |
312 # handle i18n files structure |
420 # handle i18n files structure |
313 # in the first cube given |
421 # in the first cube given |
314 print '-> preparing i18n catalogs' |
422 print '-> preparing i18n catalogs' |
315 from cubicweb.common import i18n |
423 from cubicweb import i18n |
316 langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] |
424 langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] |
317 errors = config.i18ncompile(langs) |
425 errors = config.i18ncompile(langs) |
318 if errors: |
426 if errors: |
319 print '\n'.join(errors) |
427 print '\n'.join(errors) |
320 if not ASK.confirm('error while compiling message catalogs, ' |
428 if not ASK.confirm('error while compiling message catalogs, ' |
688 mih.rewrite_configuration() |
796 mih.rewrite_configuration() |
689 # handle i18n upgrade: |
797 # handle i18n upgrade: |
690 # * install new languages |
798 # * install new languages |
691 # * recompile catalogs |
799 # * recompile catalogs |
692 # in the first componant given |
800 # in the first componant given |
693 from cubicweb.common import i18n |
801 from cubicweb import i18n |
694 templdir = cwcfg.cube_dir(config.cubes()[0]) |
802 templdir = cwcfg.cube_dir(config.cubes()[0]) |
695 langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] |
803 langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] |
696 errors = config.i18ncompile(langs) |
804 errors = config.i18ncompile(langs) |
697 if errors: |
805 if errors: |
698 print '\n'.join(errors) |
806 print '\n'.join(errors) |