1 """%%prog %s [options] %s |
1 """%%prog %s [options] %s |
2 |
2 |
3 CubicWeb main applications controller. |
3 CubicWeb main applications controller. |
4 %s""" |
4 %s""" |
5 |
5 |
6 import sys |
6 import sys |
7 from os import remove, listdir, system, kill, getpgid, pathsep |
7 from os import remove, listdir, system, kill, getpgid, pathsep |
8 from os.path import exists, join, isfile, isdir |
8 from os.path import exists, join, isfile, isdir |
10 from logilab.common.clcommands import register_commands, pop_arg |
10 from logilab.common.clcommands import register_commands, pop_arg |
11 |
11 |
12 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage |
12 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage |
13 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS |
13 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS |
14 from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm |
14 from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm |
15 |
15 |
16 def wait_process_end(pid, maxtry=10, waittime=1): |
16 def wait_process_end(pid, maxtry=10, waittime=1): |
17 """wait for a process to actually die""" |
17 """wait for a process to actually die""" |
18 import signal |
18 import signal |
19 from time import sleep |
19 from time import sleep |
20 nbtry = 0 |
20 nbtry = 0 |
40 for fname in ('data', 'views', 'views.py'): |
40 for fname in ('data', 'views', 'views.py'): |
41 if exists(join(templdir, fname)): |
41 if exists(join(templdir, fname)): |
42 modes.append('web ui') |
42 modes.append('web ui') |
43 break |
43 break |
44 return modes |
44 return modes |
45 |
45 |
46 |
46 |
47 class ApplicationCommand(Command): |
47 class ApplicationCommand(Command): |
48 """base class for command taking 0 to n application id as arguments |
48 """base class for command taking 0 to n application id as arguments |
49 (0 meaning all registered applications) |
49 (0 meaning all registered applications) |
50 """ |
50 """ |
51 arguments = '[<application>...]' |
51 arguments = '[<application>...]' |
52 options = ( |
52 options = ( |
53 ("force", |
53 ("force", |
54 {'short': 'f', 'action' : 'store_true', |
54 {'short': 'f', 'action' : 'store_true', |
55 'default': False, |
55 'default': False, |
56 'help': 'force command without asking confirmation', |
56 'help': 'force command without asking confirmation', |
57 } |
57 } |
58 ), |
58 ), |
59 ) |
59 ) |
60 actionverb = None |
60 actionverb = None |
61 |
61 |
62 def ordered_instances(self): |
62 def ordered_instances(self): |
63 """return instances in the order in which they should be started, |
63 """return instances in the order in which they should be started, |
64 considering $REGISTRY_DIR/startorder file if it exists (useful when |
64 considering $REGISTRY_DIR/startorder file if it exists (useful when |
65 some instances depends on another as external source |
65 some instances depends on another as external source |
66 """ |
66 """ |
94 # no force option |
94 # no force option |
95 askconfirm = False |
95 askconfirm = False |
96 else: |
96 else: |
97 askconfirm = False |
97 askconfirm = False |
98 self.run_args(args, askconfirm) |
98 self.run_args(args, askconfirm) |
99 |
99 |
100 def run_args(self, args, askconfirm): |
100 def run_args(self, args, askconfirm): |
101 for appid in args: |
101 for appid in args: |
102 if askconfirm: |
102 if askconfirm: |
103 print '*'*72 |
103 print '*'*72 |
104 if not confirm('%s application %r ?' % (self.name, appid)): |
104 if not confirm('%s application %r ?' % (self.name, appid)): |
105 continue |
105 continue |
106 self.run_arg(appid) |
106 self.run_arg(appid) |
107 |
107 |
108 def run_arg(self, appid): |
108 def run_arg(self, appid): |
109 cmdmeth = getattr(self, '%s_application' % self.name) |
109 cmdmeth = getattr(self, '%s_application' % self.name) |
110 try: |
110 try: |
111 cmdmeth(appid) |
111 cmdmeth(appid) |
112 except (KeyboardInterrupt, SystemExit): |
112 except (KeyboardInterrupt, SystemExit): |
141 status = system('%s %s' % (forkcmd, appid)) |
141 status = system('%s %s' % (forkcmd, appid)) |
142 if status: |
142 if status: |
143 sys.exit(status) |
143 sys.exit(status) |
144 else: |
144 else: |
145 self.run_arg(appid) |
145 self.run_arg(appid) |
146 |
146 |
147 # base commands ############################################################### |
147 # base commands ############################################################### |
148 |
148 |
149 class ListCommand(Command): |
149 class ListCommand(Command): |
150 """List configurations, componants and applications. |
150 """List configurations, componants and applications. |
151 |
151 |
153 registered applications |
153 registered applications |
154 """ |
154 """ |
155 name = 'list' |
155 name = 'list' |
156 options = ( |
156 options = ( |
157 ('verbose', |
157 ('verbose', |
158 {'short': 'v', 'action' : 'store_true', |
158 {'short': 'v', 'action' : 'store_true', |
159 'help': "display more information."}), |
159 'help': "display more information."}), |
160 ) |
160 ) |
161 |
161 |
162 def run(self, args): |
162 def run(self, args): |
163 """run the command with its specific arguments""" |
163 """run the command with its specific arguments""" |
164 if args: |
164 if args: |
165 raise BadCommandUsage('Too much arguments') |
165 raise BadCommandUsage('Too much arguments') |
166 print 'CubicWeb version:', cwcfg.cubicweb_version() |
166 print 'CubicWeb version:', cwcfg.cubicweb_version() |
172 for line in config.__doc__.splitlines(): |
172 for line in config.__doc__.splitlines(): |
173 line = line.strip() |
173 line = line.strip() |
174 if not line: |
174 if not line: |
175 continue |
175 continue |
176 print ' ', line |
176 print ' ', line |
177 print |
177 print |
178 try: |
178 try: |
179 cubesdir = pathsep.join(cwcfg.cubes_search_path()) |
179 cubesdir = pathsep.join(cwcfg.cubes_search_path()) |
180 namesize = max(len(x) for x in cwcfg.available_cubes()) |
180 namesize = max(len(x) for x in cwcfg.available_cubes()) |
181 except ConfigurationError, ex: |
181 except ConfigurationError, ex: |
182 print 'No cubes available:', ex |
182 print 'No cubes available:', ex |
217 print '* %s (BROKEN application, no configuration found)' % appid |
217 print '* %s (BROKEN application, no configuration found)' % appid |
218 continue |
218 continue |
219 print '* %s (%s)' % (appid, ', '.join(modes)) |
219 print '* %s (%s)' % (appid, ', '.join(modes)) |
220 try: |
220 try: |
221 config = cwcfg.config_for(appid, modes[0]) |
221 config = cwcfg.config_for(appid, modes[0]) |
222 except Exception, exc: |
222 except Exception, exc: |
223 print ' (BROKEN application, %s)' % exc |
223 print ' (BROKEN application, %s)' % exc |
224 continue |
224 continue |
225 else: |
225 else: |
226 print 'No application available in %s' % regdir |
226 print 'No application available in %s' % regdir |
227 print |
227 print |
259 command. Default to "all-in-one", e.g. an installation embedding both the RQL \ |
259 command. Default to "all-in-one", e.g. an installation embedding both the RQL \ |
260 repository and the web server.', |
260 repository and the web server.', |
261 } |
261 } |
262 ), |
262 ), |
263 ) |
263 ) |
264 |
264 |
265 def run(self, args): |
265 def run(self, args): |
266 """run the command with its specific arguments""" |
266 """run the command with its specific arguments""" |
267 from logilab.common.textutils import get_csv |
267 from logilab.common.textutils import get_csv |
268 configname = self.config.config |
268 configname = self.config.config |
269 cubes = get_csv(pop_arg(args, 1)) |
269 cubes = get_csv(pop_arg(args, 1)) |
321 print 'application %s (%s) created in %r' % (appid, configname, |
321 print 'application %s (%s) created in %r' % (appid, configname, |
322 config.apphome) |
322 config.apphome) |
323 print |
323 print |
324 helper.postcreate() |
324 helper.postcreate() |
325 |
325 |
326 |
326 |
327 class DeleteApplicationCommand(Command): |
327 class DeleteApplicationCommand(Command): |
328 """Delete an application. Will remove application's files and |
328 """Delete an application. Will remove application's files and |
329 unregister it. |
329 unregister it. |
330 """ |
330 """ |
331 name = 'delete' |
331 name = 'delete' |
332 arguments = '<application>' |
332 arguments = '<application>' |
333 |
333 |
334 options = () |
334 options = () |
335 |
335 |
336 def run(self, args): |
336 def run(self, args): |
337 """run the command with its specific arguments""" |
337 """run the command with its specific arguments""" |
338 appid = pop_arg(args, msg="No application specified !") |
338 appid = pop_arg(args, msg="No application specified !") |
359 |
359 |
360 # application commands ######################################################## |
360 # application commands ######################################################## |
361 |
361 |
362 class StartApplicationCommand(ApplicationCommand): |
362 class StartApplicationCommand(ApplicationCommand): |
363 """Start the given applications. If no application is given, start them all. |
363 """Start the given applications. If no application is given, start them all. |
364 |
364 |
365 <application>... |
365 <application>... |
366 identifiers of the applications to start. If no application is |
366 identifiers of the applications to start. If no application is |
367 given, start them all. |
367 given, start them all. |
368 """ |
368 """ |
369 name = 'start' |
369 name = 'start' |
412 return True |
412 return True |
413 |
413 |
414 |
414 |
415 class StopApplicationCommand(ApplicationCommand): |
415 class StopApplicationCommand(ApplicationCommand): |
416 """Stop the given applications. |
416 """Stop the given applications. |
417 |
417 |
418 <application>... |
418 <application>... |
419 identifiers of the applications to stop. If no application is |
419 identifiers of the applications to stop. If no application is |
420 given, stop them all. |
420 given, stop them all. |
421 """ |
421 """ |
422 name = 'stop' |
422 name = 'stop' |
423 actionverb = 'stopped' |
423 actionverb = 'stopped' |
424 |
424 |
425 def ordered_instances(self): |
425 def ordered_instances(self): |
426 instances = super(StopApplicationCommand, self).ordered_instances() |
426 instances = super(StopApplicationCommand, self).ordered_instances() |
427 instances.reverse() |
427 instances.reverse() |
428 return instances |
428 return instances |
429 |
429 |
430 def stop_application(self, appid): |
430 def stop_application(self, appid): |
431 """stop the application's server""" |
431 """stop the application's server""" |
432 config = cwcfg.config_for(appid) |
432 config = cwcfg.config_for(appid) |
433 helper = self.config_helper(config, cmdname='stop') |
433 helper = self.config_helper(config, cmdname='stop') |
434 helper.poststop() # do this anyway |
434 helper.poststop() # do this anyway |
458 remove(pidf) |
458 remove(pidf) |
459 except OSError: |
459 except OSError: |
460 # already removed by twistd |
460 # already removed by twistd |
461 pass |
461 pass |
462 print 'application %s stopped' % appid |
462 print 'application %s stopped' % appid |
463 |
463 |
464 |
464 |
465 class RestartApplicationCommand(StartApplicationCommand, |
465 class RestartApplicationCommand(StartApplicationCommand, |
466 StopApplicationCommand): |
466 StopApplicationCommand): |
467 """Restart the given applications. |
467 """Restart the given applications. |
468 |
468 |
469 <application>... |
469 <application>... |
470 identifiers of the applications to restart. If no application is |
470 identifiers of the applications to restart. If no application is |
471 given, restart them all. |
471 given, restart them all. |
472 """ |
472 """ |
473 name = 'restart' |
473 name = 'restart' |
495 forkcmd = ' '.join(forkcmd) |
495 forkcmd = ' '.join(forkcmd) |
496 for appid in reversed(args): |
496 for appid in reversed(args): |
497 status = system('%s %s' % (forkcmd, appid)) |
497 status = system('%s %s' % (forkcmd, appid)) |
498 if status: |
498 if status: |
499 sys.exit(status) |
499 sys.exit(status) |
500 |
500 |
501 def restart_application(self, appid): |
501 def restart_application(self, appid): |
502 self.stop_application(appid) |
502 self.stop_application(appid) |
503 if self.start_application(appid): |
503 if self.start_application(appid): |
504 print 'application %s %s' % (appid, self.actionverb) |
504 print 'application %s %s' % (appid, self.actionverb) |
505 |
505 |
506 |
506 |
507 class ReloadConfigurationCommand(RestartApplicationCommand): |
507 class ReloadConfigurationCommand(RestartApplicationCommand): |
508 """Reload the given applications. This command is equivalent to a |
508 """Reload the given applications. This command is equivalent to a |
509 restart for now. |
509 restart for now. |
510 |
510 |
511 <application>... |
511 <application>... |
512 identifiers of the applications to reload. If no application is |
512 identifiers of the applications to reload. If no application is |
513 given, reload them all. |
513 given, reload them all. |
514 """ |
514 """ |
515 name = 'reload' |
515 name = 'reload' |
516 |
516 |
517 def reload_application(self, appid): |
517 def reload_application(self, appid): |
518 self.restart_application(appid) |
518 self.restart_application(appid) |
519 |
519 |
520 |
520 |
521 class StatusCommand(ApplicationCommand): |
521 class StatusCommand(ApplicationCommand): |
522 """Display status information about the given applications. |
522 """Display status information about the given applications. |
523 |
523 |
524 <application>... |
524 <application>... |
525 identifiers of the applications to status. If no application is |
525 identifiers of the applications to status. If no application is |
526 given, get status information about all registered applications. |
526 given, get status information about all registered applications. |
527 """ |
527 """ |
528 name = 'status' |
528 name = 'status' |
574 'help': 'force migration from the indicated version for the specified cube.'}), |
574 'help': 'force migration from the indicated version for the specified cube.'}), |
575 ('force-cubicweb-version', |
575 ('force-cubicweb-version', |
576 {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', |
576 {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', |
577 'default': None, |
577 'default': None, |
578 'help': 'force migration from the indicated cubicweb version.'}), |
578 'help': 'force migration from the indicated cubicweb version.'}), |
579 |
579 |
580 ('fs-only', |
580 ('fs-only', |
581 {'short': 's', 'action' : 'store_true', |
581 {'short': 's', 'action' : 'store_true', |
582 'default': False, |
582 'default': False, |
583 'help': 'only upgrade files on the file system, not the database.'}), |
583 'help': 'only upgrade files on the file system, not the database.'}), |
584 |
584 |
585 ('nostartstop', |
585 ('nostartstop', |
586 {'short': 'n', 'action' : 'store_true', |
586 {'short': 'n', 'action' : 'store_true', |
587 'default': False, |
587 'default': False, |
588 'help': 'don\'t try to stop application before migration and to restart it after.'}), |
588 'help': 'don\'t try to stop application before migration and to restart it after.'}), |
589 |
589 |
590 ('verbosity', |
590 ('verbosity', |
591 {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', |
591 {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', |
592 'default': 1, |
592 'default': 1, |
593 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ |
593 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ |
594 for everything."}), |
594 for everything."}), |
595 |
595 |
596 ('backup-db', |
596 ('backup-db', |
597 {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>', |
597 {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>', |
598 'default': None, |
598 'default': None, |
599 'help': "Backup the application database before upgrade.\n"\ |
599 'help': "Backup the application database before upgrade.\n"\ |
600 "If the option is ommitted, confirmation will be ask.", |
600 "If the option is ommitted, confirmation will be ask.", |
611 ) |
611 ) |
612 |
612 |
613 def ordered_instances(self): |
613 def ordered_instances(self): |
614 # need this since mro return StopApplicationCommand implementation |
614 # need this since mro return StopApplicationCommand implementation |
615 return ApplicationCommand.ordered_instances(self) |
615 return ApplicationCommand.ordered_instances(self) |
616 |
616 |
617 def upgrade_application(self, appid): |
617 def upgrade_application(self, appid): |
618 from logilab.common.changelog import Version |
618 from logilab.common.changelog import Version |
619 if not (cwcfg.mode == 'dev' or self.config.nostartstop): |
619 if not (cwcfg.mode == 'dev' or self.config.nostartstop): |
620 self.stop_application(appid) |
620 self.stop_application(appid) |
621 config = cwcfg.config_for(appid) |
621 config = cwcfg.config_for(appid) |
646 except KeyError: |
646 except KeyError: |
647 config.error('no version information for %s' % cube) |
647 config.error('no version information for %s' % cube) |
648 continue |
648 continue |
649 if installedversion > applversion: |
649 if installedversion > applversion: |
650 toupgrade.append( (cube, applversion, installedversion) ) |
650 toupgrade.append( (cube, applversion, installedversion) ) |
651 cubicwebversion = config.cubicweb_version() |
651 cubicwebversion = config.cubicweb_version() |
652 if self.config.force_cubicweb_version: |
652 if self.config.force_cubicweb_version: |
653 applcubicwebversion = Version(self.config.force_cubicweb_version) |
653 applcubicwebversion = Version(self.config.force_cubicweb_version) |
654 vcconf['cubicweb'] = applcubicwebversion |
654 vcconf['cubicweb'] = applcubicwebversion |
655 else: |
655 else: |
656 applcubicwebversion = vcconf.get('cubicweb') |
656 applcubicwebversion = vcconf.get('cubicweb') |
704 {'short': 'S', 'action' : 'store_true', |
704 {'short': 'S', 'action' : 'store_true', |
705 'default': False, |
705 'default': False, |
706 'help': 'only connect to the system source when the instance is ' |
706 'help': 'only connect to the system source when the instance is ' |
707 'using multiple sources. You can\'t use this option and the ' |
707 'using multiple sources. You can\'t use this option and the ' |
708 '--ext-sources option at the same time.'}), |
708 '--ext-sources option at the same time.'}), |
709 |
709 |
710 ('ext-sources', |
710 ('ext-sources', |
711 {'short': 'E', 'type' : 'csv', 'metavar': '<sources>', |
711 {'short': 'E', 'type' : 'csv', 'metavar': '<sources>', |
712 'default': None, |
712 'default': None, |
713 'help': "For multisources instances, specify to which sources the \ |
713 'help': "For multisources instances, specify to which sources the \ |
714 repository should connect to for upgrading. When unspecified or 'all' given, \ |
714 repository should connect to for upgrading. When unspecified or 'all' given, \ |
715 will connect to all defined sources. If 'migration' is given, appropriate \ |
715 will connect to all defined sources. If 'migration' is given, appropriate \ |
716 sources for migration will be automatically selected.", |
716 sources for migration will be automatically selected.", |
717 }), |
717 }), |
718 |
718 |
719 ) |
719 ) |
720 def run(self, args): |
720 def run(self, args): |
721 appid = pop_arg(args, 99, msg="No application specified !") |
721 appid = pop_arg(args, 99, msg="No application specified !") |
722 config = cwcfg.config_for(appid) |
722 config = cwcfg.config_for(appid) |
723 if self.config.ext_sources: |
723 if self.config.ext_sources: |
731 mih = config.migration_handler() |
731 mih = config.migration_handler() |
732 if args: |
732 if args: |
733 mih.scripts_session(args) |
733 mih.scripts_session(args) |
734 else: |
734 else: |
735 mih.interactive_shell() |
735 mih.interactive_shell() |
736 mih.shutdown() |
736 mih.shutdown() |
737 |
737 |
738 |
738 |
739 class RecompileApplicationCatalogsCommand(ApplicationCommand): |
739 class RecompileApplicationCatalogsCommand(ApplicationCommand): |
740 """Recompile i18n catalogs for applications. |
740 """Recompile i18n catalogs for applications. |
741 |
741 |
742 <application>... |
742 <application>... |
743 identifiers of the applications to consider. If no application is |
743 identifiers of the applications to consider. If no application is |
744 given, recompile for all registered applications. |
744 given, recompile for all registered applications. |
745 """ |
745 """ |
746 name = 'i18ncompile' |
746 name = 'i18ncompile' |
770 |
770 |
771 class ListInstancesCommand(Command): |
771 class ListInstancesCommand(Command): |
772 """list available instances, useful for bash completion.""" |
772 """list available instances, useful for bash completion.""" |
773 name = 'listinstances' |
773 name = 'listinstances' |
774 hidden = True |
774 hidden = True |
775 |
775 |
776 def run(self, args): |
776 def run(self, args): |
777 """run the command with its specific arguments""" |
777 """run the command with its specific arguments""" |
778 regdir = cwcfg.registry_dir() |
778 regdir = cwcfg.registry_dir() |
779 for appid in sorted(listdir(regdir)): |
779 for appid in sorted(listdir(regdir)): |
780 print appid |
780 print appid |
802 ShellCommand, |
802 ShellCommand, |
803 RecompileApplicationCatalogsCommand, |
803 RecompileApplicationCatalogsCommand, |
804 ListInstancesCommand, ListCubesCommand, |
804 ListInstancesCommand, ListCubesCommand, |
805 )) |
805 )) |
806 |
806 |
807 |
807 |
808 def run(args): |
808 def run(args): |
809 """command line tool""" |
809 """command line tool""" |
810 cwcfg.load_cwctl_plugins() |
810 cwcfg.load_cwctl_plugins() |
811 main_run(args, __doc__) |
811 main_run(args, __doc__) |
812 |
812 |