|
1 """%%prog %s [options] %s |
|
2 |
|
3 CubicWeb main applications controller. |
|
4 %s""" |
|
5 |
|
6 import sys |
|
7 from os import remove, listdir, system, kill, getpgid |
|
8 from os.path import exists, join, isfile, isdir |
|
9 |
|
10 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage |
|
11 from cubicweb.cwconfig import CubicWebConfiguration, CONFIGURATIONS |
|
12 from cubicweb.toolsutils import (Command, register_commands, main_run, |
|
13 rm, create_dir, pop_arg, confirm) |
|
14 |
|
15 def wait_process_end(pid, maxtry=10, waittime=1): |
|
16 """wait for a process to actually die""" |
|
17 import signal |
|
18 from time import sleep |
|
19 nbtry = 0 |
|
20 while nbtry < maxtry: |
|
21 try: |
|
22 kill(pid, signal.SIGUSR1) |
|
23 except OSError: |
|
24 break |
|
25 nbtry += 1 |
|
26 sleep(waittime) |
|
27 else: |
|
28 raise ExecutionError('can\'t kill process %s' % pid) |
|
29 |
|
30 def list_instances(regdir): |
|
31 return sorted(idir for idir in listdir(regdir) if isdir(join(regdir, idir))) |
|
32 |
|
33 def detect_available_modes(templdir): |
|
34 modes = [] |
|
35 for fname in ('schema', 'schema.py'): |
|
36 if exists(join(templdir, fname)): |
|
37 modes.append('repository') |
|
38 break |
|
39 for fname in ('data', 'views', 'views.py'): |
|
40 if exists(join(templdir, fname)): |
|
41 modes.append('web ui') |
|
42 break |
|
43 return modes |
|
44 |
|
45 |
|
46 class ApplicationCommand(Command): |
|
47 """base class for command taking 0 to n application id as arguments |
|
48 (0 meaning all registered applications) |
|
49 """ |
|
50 arguments = '[<application>...]' |
|
51 options = ( |
|
52 ("force", |
|
53 {'short': 'f', 'action' : 'store_true', |
|
54 'default': False, |
|
55 'help': 'force command without asking confirmation', |
|
56 } |
|
57 ), |
|
58 ) |
|
59 actionverb = None |
|
60 |
|
61 def ordered_instances(self): |
|
62 """return instances in the order in which they should be started, |
|
63 considering $REGISTRY_DIR/startorder file if it exists (useful when |
|
64 some instances depends on another as external source |
|
65 """ |
|
66 regdir = CubicWebConfiguration.registry_dir() |
|
67 _allinstances = list_instances(regdir) |
|
68 if isfile(join(regdir, 'startorder')): |
|
69 allinstances = [] |
|
70 for line in file(join(regdir, 'startorder')): |
|
71 line = line.strip() |
|
72 if line and not line.startswith('#'): |
|
73 try: |
|
74 _allinstances.remove(line) |
|
75 allinstances.append(line) |
|
76 except ValueError: |
|
77 print 'ERROR: startorder file contains unexistant instance %s' % line |
|
78 allinstances += _allinstances |
|
79 else: |
|
80 allinstances = _allinstances |
|
81 return allinstances |
|
82 |
|
83 def run(self, args): |
|
84 """run the <command>_method on each argument (a list of application |
|
85 identifiers) |
|
86 """ |
|
87 if not args: |
|
88 args = self.ordered_instances() |
|
89 try: |
|
90 askconfirm = not self.config.force |
|
91 except AttributeError: |
|
92 # no force option |
|
93 askconfirm = False |
|
94 else: |
|
95 askconfirm = False |
|
96 self.run_args(args, askconfirm) |
|
97 |
|
98 def run_args(self, args, askconfirm): |
|
99 for appid in args: |
|
100 if askconfirm: |
|
101 print '*'*72 |
|
102 if not confirm('%s application %r ?' % (self.name, appid)): |
|
103 continue |
|
104 self.run_arg(appid) |
|
105 |
|
106 def run_arg(self, appid): |
|
107 cmdmeth = getattr(self, '%s_application' % self.name) |
|
108 try: |
|
109 cmdmeth(appid) |
|
110 except (KeyboardInterrupt, SystemExit): |
|
111 print >> sys.stderr, '%s aborted' % self.name |
|
112 sys.exit(2) # specific error code |
|
113 except (ExecutionError, ConfigurationError), ex: |
|
114 print >> sys.stderr, 'application %s not %s: %s' % ( |
|
115 appid, self.actionverb, ex) |
|
116 except Exception, ex: |
|
117 import traceback |
|
118 traceback.print_exc() |
|
119 print >> sys.stderr, 'application %s not %s: %s' % ( |
|
120 appid, self.actionverb, ex) |
|
121 |
|
122 |
|
123 class ApplicationCommandFork(ApplicationCommand): |
|
124 """Same as `ApplicationCommand`, but command is forked in a new environment |
|
125 for each argument |
|
126 """ |
|
127 |
|
128 def run_args(self, args, askconfirm): |
|
129 if len(args) > 1: |
|
130 forkcmd = ' '.join(w for w in sys.argv if not w in args) |
|
131 else: |
|
132 forkcmd = None |
|
133 for appid in args: |
|
134 if askconfirm: |
|
135 print '*'*72 |
|
136 if not confirm('%s application %r ?' % (self.name, appid)): |
|
137 continue |
|
138 if forkcmd: |
|
139 status = system('%s %s' % (forkcmd, appid)) |
|
140 if status: |
|
141 sys.exit(status) |
|
142 else: |
|
143 self.run_arg(appid) |
|
144 |
|
145 # base commands ############################################################### |
|
146 |
|
147 class ListCommand(Command): |
|
148 """List configurations, componants and applications. |
|
149 |
|
150 list available configurations, installed web and server componants, and |
|
151 registered applications |
|
152 """ |
|
153 name = 'list' |
|
154 options = ( |
|
155 ('verbose', |
|
156 {'short': 'v', 'action' : 'store_true', |
|
157 'help': "display more information."}), |
|
158 ) |
|
159 |
|
160 def run(self, args): |
|
161 """run the command with its specific arguments""" |
|
162 if args: |
|
163 raise BadCommandUsage('Too much arguments') |
|
164 print 'CubicWeb version:', CubicWebConfiguration.cubicweb_version() |
|
165 print 'Detected mode:', CubicWebConfiguration.mode |
|
166 print |
|
167 print 'Available configurations:' |
|
168 for config in CONFIGURATIONS: |
|
169 print '*', config.name |
|
170 for line in config.__doc__.splitlines(): |
|
171 line = line.strip() |
|
172 if not line: |
|
173 continue |
|
174 print ' ', line |
|
175 print |
|
176 try: |
|
177 cubesdir = CubicWebConfiguration.cubes_dir() |
|
178 namesize = max(len(x) for x in CubicWebConfiguration.available_cubes()) |
|
179 except ConfigurationError, ex: |
|
180 print 'No cubes available:', ex |
|
181 except ValueError: |
|
182 print 'No cubes available in %s' % cubesdir |
|
183 else: |
|
184 print 'Available cubes (%s):' % cubesdir |
|
185 for cube in CubicWebConfiguration.available_cubes(): |
|
186 if cube in ('CVS', '.svn', 'shared', '.hg'): |
|
187 continue |
|
188 templdir = join(cubesdir, cube) |
|
189 try: |
|
190 tinfo = CubicWebConfiguration.cube_pkginfo(cube) |
|
191 tversion = tinfo.version |
|
192 except ConfigurationError: |
|
193 tinfo = None |
|
194 tversion = '[missing cube information]' |
|
195 print '* %s %s' % (cube.ljust(namesize), tversion) |
|
196 if self.config.verbose: |
|
197 shortdesc = tinfo and (getattr(tinfo, 'short_desc', '') |
|
198 or tinfo.__doc__) |
|
199 if shortdesc: |
|
200 print ' '+ ' \n'.join(shortdesc.splitlines()) |
|
201 modes = detect_available_modes(templdir) |
|
202 print ' available modes: %s' % ', '.join(modes) |
|
203 print |
|
204 try: |
|
205 regdir = CubicWebConfiguration.registry_dir() |
|
206 except ConfigurationError, ex: |
|
207 print 'No application available:', ex |
|
208 print |
|
209 return |
|
210 instances = list_instances(regdir) |
|
211 if instances: |
|
212 print 'Available applications (%s):' % regdir |
|
213 for appid in instances: |
|
214 modes = CubicWebConfiguration.possible_configurations(appid) |
|
215 if not modes: |
|
216 print '* %s (BROKEN application, no configuration found)' % appid |
|
217 continue |
|
218 print '* %s (%s)' % (appid, ', '.join(modes)) |
|
219 try: |
|
220 config = CubicWebConfiguration.config_for(appid, modes[0]) |
|
221 except Exception, exc: |
|
222 print ' (BROKEN application, %s)' % exc |
|
223 continue |
|
224 else: |
|
225 print 'No application available in %s' % regdir |
|
226 print |
|
227 |
|
228 |
|
229 class CreateApplicationCommand(Command): |
|
230 """Create an application from a cube. This is an unified |
|
231 command which can handle web / server / all-in-one installation |
|
232 according to available parts of the software library and of the |
|
233 desired cube. |
|
234 |
|
235 <cube> |
|
236 the name of cube to use (list available cube names using |
|
237 the "list" command). You can use several cubes by separating |
|
238 them using comma (e.g. 'jpl,eemail') |
|
239 <application> |
|
240 an identifier for the application to create |
|
241 """ |
|
242 name = 'create' |
|
243 arguments = '<cube> <application>' |
|
244 options = ( |
|
245 ("config-level", |
|
246 {'short': 'l', 'type' : 'int', 'metavar': '<level>', |
|
247 'default': 0, |
|
248 'help': 'configuration level (0..2): 0 will ask for essential \ |
|
249 configuration parameters only while 2 will ask for all parameters', |
|
250 } |
|
251 ), |
|
252 ("config", |
|
253 {'short': 'c', 'type' : 'choice', 'metavar': '<install type>', |
|
254 'choices': ('all-in-one', 'repository', 'twisted'), |
|
255 'default': 'all-in-one', |
|
256 'help': 'installation type, telling which part of an application \ |
|
257 should be installed. You can list available configurations using the "list" \ |
|
258 command. Default to "all-in-one", e.g. an installation embedding both the RQL \ |
|
259 repository and the web server.', |
|
260 } |
|
261 ), |
|
262 ) |
|
263 |
|
264 def run(self, args): |
|
265 """run the command with its specific arguments""" |
|
266 from logilab.common.textutils import get_csv |
|
267 configname = self.config.config |
|
268 cubes = get_csv(pop_arg(args, 1)) |
|
269 appid = pop_arg(args) |
|
270 # get the configuration and helper |
|
271 CubicWebConfiguration.creating = True |
|
272 config = CubicWebConfiguration.config_for(appid, configname) |
|
273 config.set_language = False |
|
274 config.init_cubes(config.expand_cubes(cubes)) |
|
275 helper = self.config_helper(config) |
|
276 # check the cube exists |
|
277 try: |
|
278 templdirs = [CubicWebConfiguration.cube_dir(cube) |
|
279 for cube in cubes] |
|
280 except ConfigurationError, ex: |
|
281 print ex |
|
282 print '\navailable cubes:', |
|
283 print ', '.join(CubicWebConfiguration.available_cubes()) |
|
284 return |
|
285 # create the registry directory for this application |
|
286 create_dir(config.apphome) |
|
287 # load site_cubicweb from the cubes dir (if any) |
|
288 config.load_site_cubicweb() |
|
289 # cubicweb-ctl configuration |
|
290 print '** application\'s %s configuration' % configname |
|
291 print '-' * 72 |
|
292 config.input_config('main', self.config.config_level) |
|
293 # configuration'specific stuff |
|
294 print |
|
295 helper.bootstrap(cubes, self.config.config_level) |
|
296 # write down configuration |
|
297 config.save() |
|
298 # handle i18n files structure |
|
299 # XXX currently available languages are guessed from translations found |
|
300 # in the first cube given |
|
301 from cubicweb.common import i18n |
|
302 langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] |
|
303 errors = config.i18ncompile(langs) |
|
304 if errors: |
|
305 print '\n'.join(errors) |
|
306 if not confirm('error while compiling message catalogs, ' |
|
307 'continue anyway ?'): |
|
308 print 'creation not completed' |
|
309 return |
|
310 # create the additional data directory for this application |
|
311 if config.appdatahome != config.apphome: # true in dev mode |
|
312 create_dir(config.appdatahome) |
|
313 if config['uid']: |
|
314 from logilab.common.shellutils import chown |
|
315 # this directory should be owned by the uid of the server process |
|
316 print 'set %s as owner of the data directory' % config['uid'] |
|
317 chown(config.appdatahome, config['uid']) |
|
318 print |
|
319 print |
|
320 print '*' * 72 |
|
321 print 'application %s (%s) created in %r' % (appid, configname, |
|
322 config.apphome) |
|
323 print |
|
324 helper.postcreate() |
|
325 |
|
326 |
|
327 class DeleteApplicationCommand(Command): |
|
328 """Delete an application. Will remove application's files and |
|
329 unregister it. |
|
330 """ |
|
331 name = 'delete' |
|
332 arguments = '<application>' |
|
333 |
|
334 options = () |
|
335 |
|
336 def run(self, args): |
|
337 """run the command with its specific arguments""" |
|
338 appid = pop_arg(args, msg="No application specified !") |
|
339 configs = [CubicWebConfiguration.config_for(appid, configname) |
|
340 for configname in CubicWebConfiguration.possible_configurations(appid)] |
|
341 if not configs: |
|
342 raise ExecutionError('unable to guess configuration for %s' % appid) |
|
343 for config in configs: |
|
344 helper = self.config_helper(config, required=False) |
|
345 if helper: |
|
346 helper.cleanup() |
|
347 # remove home |
|
348 rm(config.apphome) |
|
349 # remove instance data directory |
|
350 try: |
|
351 rm(config.appdatahome) |
|
352 except OSError, ex: |
|
353 import errno |
|
354 if ex.errno != errno.ENOENT: |
|
355 raise |
|
356 confignames = ', '.join([config.name for config in configs]) |
|
357 print 'application %s (%s) deleted' % (appid, confignames) |
|
358 |
|
359 |
|
360 # application commands ######################################################## |
|
361 |
|
362 class StartApplicationCommand(ApplicationCommand): |
|
363 """Start the given applications. If no application is given, start them all. |
|
364 |
|
365 <application>... |
|
366 identifiers of the applications to start. If no application is |
|
367 given, start them all. |
|
368 """ |
|
369 name = 'start' |
|
370 actionverb = 'started' |
|
371 options = ( |
|
372 ("debug", |
|
373 {'short': 'D', 'action' : 'store_true', |
|
374 'help': 'start server in debug mode.'}), |
|
375 ("force", |
|
376 {'short': 'f', 'action' : 'store_true', |
|
377 'default': False, |
|
378 'help': 'start the application even if it seems to be already \ |
|
379 running.'}), |
|
380 ('profile', |
|
381 {'short': 'P', 'type' : 'string', 'metavar': '<stat file>', |
|
382 'default': None, |
|
383 'help': 'profile code and use the specified file to store stats', |
|
384 }), |
|
385 ) |
|
386 |
|
387 def start_application(self, appid): |
|
388 """start the application's server""" |
|
389 # use get() since start may be used from other commands (eg upgrade) |
|
390 # without all options defined |
|
391 debug = self.get('debug') |
|
392 force = self.get('force') |
|
393 config = CubicWebConfiguration.config_for(appid) |
|
394 if self.get('profile'): |
|
395 config.global_set_option('profile', self.config.profile) |
|
396 helper = self.config_helper(config, cmdname='start') |
|
397 pidf = config['pid-file'] |
|
398 if exists(pidf) and not force: |
|
399 msg = "%s seems to be running. Remove %s by hand if necessary or use \ |
|
400 the --force option." |
|
401 raise ExecutionError(msg % (appid, pidf)) |
|
402 command = helper.start_command(config, debug) |
|
403 if debug: |
|
404 print "starting server with command :" |
|
405 print command |
|
406 if system(command): |
|
407 print 'an error occured while starting the application, not started' |
|
408 print |
|
409 return False |
|
410 if not debug: |
|
411 print 'application %s started' % appid |
|
412 return True |
|
413 |
|
414 |
|
415 class StopApplicationCommand(ApplicationCommand): |
|
416 """Stop the given applications. |
|
417 |
|
418 <application>... |
|
419 identifiers of the applications to stop. If no application is |
|
420 given, stop them all. |
|
421 """ |
|
422 name = 'stop' |
|
423 actionverb = 'stopped' |
|
424 |
|
425 def ordered_instances(self): |
|
426 instances = super(StopApplicationCommand, self).ordered_instances() |
|
427 instances.reverse() |
|
428 return instances |
|
429 |
|
430 def stop_application(self, appid): |
|
431 """stop the application's server""" |
|
432 config = CubicWebConfiguration.config_for(appid) |
|
433 helper = self.config_helper(config, cmdname='stop') |
|
434 helper.poststop() # do this anyway |
|
435 pidf = config['pid-file'] |
|
436 if not exists(pidf): |
|
437 print >> sys.stderr, "%s doesn't exist." % pidf |
|
438 return |
|
439 import signal |
|
440 pid = int(open(pidf).read().strip()) |
|
441 try: |
|
442 kill(pid, signal.SIGTERM) |
|
443 except: |
|
444 print >> sys.stderr, "process %s seems already dead." % pid |
|
445 else: |
|
446 try: |
|
447 wait_process_end(pid) |
|
448 except ExecutionError, ex: |
|
449 print >> sys.stderr, ex |
|
450 print >> sys.stderr, 'trying SIGKILL' |
|
451 try: |
|
452 kill(pid, signal.SIGKILL) |
|
453 except: |
|
454 # probably dead now |
|
455 pass |
|
456 wait_process_end(pid) |
|
457 try: |
|
458 remove(pidf) |
|
459 except OSError: |
|
460 # already removed by twistd |
|
461 pass |
|
462 print 'application %s stopped' % appid |
|
463 |
|
464 |
|
465 class RestartApplicationCommand(StartApplicationCommand, |
|
466 StopApplicationCommand): |
|
467 """Restart the given applications. |
|
468 |
|
469 <application>... |
|
470 identifiers of the applications to restart. If no application is |
|
471 given, restart them all. |
|
472 """ |
|
473 name = 'restart' |
|
474 actionverb = 'restarted' |
|
475 |
|
476 def run_args(self, args, askconfirm): |
|
477 regdir = CubicWebConfiguration.registry_dir() |
|
478 if not isfile(join(regdir, 'startorder')) or len(args) <= 1: |
|
479 # no specific startorder |
|
480 super(RestartApplicationCommand, self).run_args(args, askconfirm) |
|
481 return |
|
482 print ('some specific start order is specified, will first stop all ' |
|
483 'applications then restart them.') |
|
484 # get instances in startorder |
|
485 stopped = [] |
|
486 for appid in args: |
|
487 if askconfirm: |
|
488 print '*'*72 |
|
489 if not confirm('%s application %r ?' % (self.name, appid)): |
|
490 continue |
|
491 self.stop_application(appid) |
|
492 stopped.append(appid) |
|
493 forkcmd = [w for w in sys.argv if not w in args] |
|
494 forkcmd[1] = 'start' |
|
495 forkcmd = ' '.join(forkcmd) |
|
496 for appid in reversed(args): |
|
497 status = system('%s %s' % (forkcmd, appid)) |
|
498 if status: |
|
499 sys.exit(status) |
|
500 |
|
501 def restart_application(self, appid): |
|
502 self.stop_application(appid) |
|
503 if self.start_application(appid): |
|
504 print 'application %s %s' % (appid, self.actionverb) |
|
505 |
|
506 |
|
507 class ReloadConfigurationCommand(RestartApplicationCommand): |
|
508 """Reload the given applications. This command is equivalent to a |
|
509 restart for now. |
|
510 |
|
511 <application>... |
|
512 identifiers of the applications to reload. If no application is |
|
513 given, reload them all. |
|
514 """ |
|
515 name = 'reload' |
|
516 |
|
517 def reload_application(self, appid): |
|
518 self.restart_application(appid) |
|
519 |
|
520 |
|
521 class StatusCommand(ApplicationCommand): |
|
522 """Display status information about the given applications. |
|
523 |
|
524 <application>... |
|
525 identifiers of the applications to status. If no application is |
|
526 given, get status information about all registered applications. |
|
527 """ |
|
528 name = 'status' |
|
529 options = () |
|
530 |
|
531 def status_application(self, appid): |
|
532 """print running status information for an application""" |
|
533 for mode in CubicWebConfiguration.possible_configurations(appid): |
|
534 config = CubicWebConfiguration.config_for(appid, mode) |
|
535 print '[%s-%s]' % (appid, mode), |
|
536 try: |
|
537 pidf = config['pid-file'] |
|
538 except KeyError: |
|
539 print 'buggy application, pid file not specified' |
|
540 continue |
|
541 if not exists(pidf): |
|
542 print "doesn't seem to be running" |
|
543 continue |
|
544 pid = int(open(pidf).read().strip()) |
|
545 # trick to guess whether or not the process is running |
|
546 try: |
|
547 getpgid(pid) |
|
548 except OSError: |
|
549 print "should be running with pid %s but the process can not be found" % pid |
|
550 continue |
|
551 print "running with pid %s" % (pid) |
|
552 |
|
553 |
|
554 class UpgradeApplicationCommand(ApplicationCommandFork, |
|
555 StartApplicationCommand, |
|
556 StopApplicationCommand): |
|
557 """Upgrade an application after cubicweb and/or component(s) upgrade. |
|
558 |
|
559 For repository update, you will be prompted for a login / password to use |
|
560 to connect to the system database. For some upgrades, the given user |
|
561 should have create or alter table permissions. |
|
562 |
|
563 <application>... |
|
564 identifiers of the applications to upgrade. If no application is |
|
565 given, upgrade them all. |
|
566 """ |
|
567 name = 'upgrade' |
|
568 actionverb = 'upgraded' |
|
569 options = ApplicationCommand.options + ( |
|
570 ('force-componant-version', |
|
571 {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z', |
|
572 'default': None, |
|
573 'help': 'force migration from the indicated version for the specified cube.'}), |
|
574 ('force-cubicweb-version', |
|
575 {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', |
|
576 'default': None, |
|
577 'help': 'force migration from the indicated cubicweb version.'}), |
|
578 |
|
579 ('fs-only', |
|
580 {'short': 's', 'action' : 'store_true', |
|
581 'default': False, |
|
582 'help': 'only upgrade files on the file system, not the database.'}), |
|
583 |
|
584 ('nostartstop', |
|
585 {'short': 'n', 'action' : 'store_true', |
|
586 'default': False, |
|
587 'help': 'don\'t try to stop application before migration and to restart it after.'}), |
|
588 |
|
589 ('verbosity', |
|
590 {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', |
|
591 'default': 1, |
|
592 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ |
|
593 for everything."}), |
|
594 |
|
595 ('backup-db', |
|
596 {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>', |
|
597 'default': None, |
|
598 'help': "Backup the application database before upgrade.\n"\ |
|
599 "If the option is ommitted, confirmation will be ask.", |
|
600 }), |
|
601 |
|
602 ('ext-sources', |
|
603 {'short': 'E', 'type' : 'csv', 'metavar': '<sources>', |
|
604 'default': None, |
|
605 'help': "For multisources instances, specify to which sources the \ |
|
606 repository should connect to for upgrading. When unspecified or 'migration' is \ |
|
607 given, appropriate sources for migration will be automatically selected \ |
|
608 (recommended). If 'all' is given, will connect to all defined sources.", |
|
609 }), |
|
610 ) |
|
611 |
|
612 def ordered_instances(self): |
|
613 # need this since mro return StopApplicationCommand implementation |
|
614 return ApplicationCommand.ordered_instances(self) |
|
615 |
|
616 def upgrade_application(self, appid): |
|
617 from logilab.common.changelog import Version |
|
618 if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop): |
|
619 self.stop_application(appid) |
|
620 config = CubicWebConfiguration.config_for(appid) |
|
621 config.creating = True # notice we're not starting the server |
|
622 config.verbosity = self.config.verbosity |
|
623 config.set_sources_mode(self.config.ext_sources or ('migration',)) |
|
624 # get application and installed versions for the server and the componants |
|
625 print 'getting versions configuration from the repository...' |
|
626 mih = config.migration_handler() |
|
627 repo = mih.repo_connect() |
|
628 vcconf = repo.get_versions() |
|
629 print 'done' |
|
630 if self.config.force_componant_version: |
|
631 packversions = {} |
|
632 for vdef in self.config.force_componant_version: |
|
633 componant, version = vdef.split('=') |
|
634 packversions[componant] = Version(version) |
|
635 vcconf.update(packversions) |
|
636 toupgrade = [] |
|
637 for cube in config.cubes(): |
|
638 installedversion = config.cube_version(cube) |
|
639 try: |
|
640 applversion = vcconf[cube] |
|
641 except KeyError: |
|
642 config.error('no version information for %s' % cube) |
|
643 continue |
|
644 if installedversion > applversion: |
|
645 toupgrade.append( (cube, applversion, installedversion) ) |
|
646 cubicwebversion = config.cubicweb_version() |
|
647 if self.config.force_cubicweb_version: |
|
648 applcubicwebversion = Version(self.config.force_cubicweb_version) |
|
649 vcconf['cubicweb'] = applcubicwebversion |
|
650 else: |
|
651 applcubicwebversion = vcconf.get('cubicweb') |
|
652 if cubicwebversion > applcubicwebversion: |
|
653 toupgrade.append( ('cubicweb', applcubicwebversion, cubicwebversion) ) |
|
654 if not self.config.fs_only and not toupgrade: |
|
655 print 'no software migration needed for application %s' % appid |
|
656 return |
|
657 for cube, fromversion, toversion in toupgrade: |
|
658 print '**** %s migration %s -> %s' % (cube, fromversion, toversion) |
|
659 # run cubicweb/componants migration scripts |
|
660 mih.migrate(vcconf, reversed(toupgrade), self.config) |
|
661 # rewrite main configuration file |
|
662 mih.rewrite_configuration() |
|
663 # handle i18n upgrade: |
|
664 # * install new languages |
|
665 # * recompile catalogs |
|
666 # XXX currently available languages are guessed from translations found |
|
667 # in the first componant given |
|
668 from cubicweb.common import i18n |
|
669 templdir = CubicWebConfiguration.cube_dir(config.cubes()[0]) |
|
670 langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] |
|
671 errors = config.i18ncompile(langs) |
|
672 if errors: |
|
673 print '\n'.join(errors) |
|
674 if not confirm('error while compiling message catalogs, ' |
|
675 'continue anyway ?'): |
|
676 print 'migration not completed' |
|
677 return |
|
678 mih.rewrite_vcconfiguration() |
|
679 mih.shutdown() |
|
680 print |
|
681 print 'application migrated' |
|
682 if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop): |
|
683 self.start_application(appid) |
|
684 print |
|
685 |
|
686 |
|
687 class ShellCommand(Command): |
|
688 """Run an interactive migration shell. This is a python shell with |
|
689 enhanced migration commands predefined in the namespace. An additional |
|
690 argument may be given corresponding to a file containing commands to |
|
691 execute in batch mode. |
|
692 |
|
693 <application> |
|
694 the identifier of the application to connect. |
|
695 """ |
|
696 name = 'shell' |
|
697 arguments = '<application> [batch command file]' |
|
698 options = ( |
|
699 ('system-only', |
|
700 {'short': 'S', 'action' : 'store_true', |
|
701 'default': False, |
|
702 'help': 'only connect to the system source when the instance is ' |
|
703 'using multiple sources. You can\'t use this option and the ' |
|
704 '--ext-sources option at the same time.'}), |
|
705 |
|
706 ('ext-sources', |
|
707 {'short': 'E', 'type' : 'csv', 'metavar': '<sources>', |
|
708 'default': None, |
|
709 'help': "For multisources instances, specify to which sources the \ |
|
710 repository should connect to for upgrading. When unspecified or 'all' given, \ |
|
711 will connect to all defined sources. If 'migration' is given, appropriate \ |
|
712 sources for migration will be automatically selected.", |
|
713 }), |
|
714 |
|
715 ) |
|
716 def run(self, args): |
|
717 appid = pop_arg(args, 99, msg="No application specified !") |
|
718 config = CubicWebConfiguration.config_for(appid) |
|
719 if self.config.ext_sources: |
|
720 assert not self.config.system_only |
|
721 sources = self.config.ext_sources |
|
722 elif self.config.system_only: |
|
723 sources = ('system',) |
|
724 else: |
|
725 sources = ('all',) |
|
726 config.set_sources_mode(sources) |
|
727 mih = config.migration_handler() |
|
728 if args: |
|
729 mih.scripts_session(args) |
|
730 else: |
|
731 mih.interactive_shell() |
|
732 mih.shutdown() |
|
733 |
|
734 |
|
735 class RecompileApplicationCatalogsCommand(ApplicationCommand): |
|
736 """Recompile i18n catalogs for applications. |
|
737 |
|
738 <application>... |
|
739 identifiers of the applications to consider. If no application is |
|
740 given, recompile for all registered applications. |
|
741 """ |
|
742 name = 'i18ncompile' |
|
743 |
|
744 def i18ncompile_application(self, appid): |
|
745 """recompile application's messages catalogs""" |
|
746 config = CubicWebConfiguration.config_for(appid) |
|
747 try: |
|
748 config.bootstrap_cubes() |
|
749 except IOError, ex: |
|
750 import errno |
|
751 if ex.errno != errno.ENOENT: |
|
752 raise |
|
753 # bootstrap_cubes files doesn't exist |
|
754 # set creating to notify this is not a regular start |
|
755 config.creating = True |
|
756 # create an in-memory repository, will call config.init_cubes() |
|
757 config.repository() |
|
758 except AttributeError: |
|
759 # web only config |
|
760 config.init_cubes(config.repository().get_cubes()) |
|
761 errors = config.i18ncompile() |
|
762 if errors: |
|
763 print '\n'.join(errors) |
|
764 |
|
765 |
|
766 class ListInstancesCommand(Command): |
|
767 """list available instances, useful for bash completion.""" |
|
768 name = 'listinstances' |
|
769 hidden = True |
|
770 |
|
771 def run(self, args): |
|
772 """run the command with its specific arguments""" |
|
773 regdir = CubicWebConfiguration.registry_dir() |
|
774 for appid in sorted(listdir(regdir)): |
|
775 print appid |
|
776 |
|
777 |
|
778 class ListCubesCommand(Command): |
|
779 """list available componants, useful for bash completion.""" |
|
780 name = 'listcubes' |
|
781 hidden = True |
|
782 |
|
783 def run(self, args): |
|
784 """run the command with its specific arguments""" |
|
785 for cube in CubicWebConfiguration.available_cubes(): |
|
786 print cube |
|
787 |
|
788 register_commands((ListCommand, |
|
789 CreateApplicationCommand, |
|
790 DeleteApplicationCommand, |
|
791 StartApplicationCommand, |
|
792 StopApplicationCommand, |
|
793 RestartApplicationCommand, |
|
794 ReloadConfigurationCommand, |
|
795 StatusCommand, |
|
796 UpgradeApplicationCommand, |
|
797 ShellCommand, |
|
798 RecompileApplicationCatalogsCommand, |
|
799 ListInstancesCommand, ListCubesCommand, |
|
800 )) |
|
801 |
|
802 |
|
803 def run(args): |
|
804 """command line tool""" |
|
805 CubicWebConfiguration.load_cwctl_plugins() |
|
806 main_run(args, __doc__) |
|
807 |
|
808 if __name__ == '__main__': |
|
809 run(sys.argv[1:]) |