cwctl.py
branchtls-sprint
changeset 1446 e951a860eeaf
parent 1342 4273e44852cb
child 1477 b056a49c16dc
equal deleted inserted replaced
1437:ea75dfe32317 1446:e951a860eeaf
     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         """
    79                                'instance %s' % line)
    79                                'instance %s' % line)
    80             allinstances += _allinstances
    80             allinstances += _allinstances
    81         else:
    81         else:
    82             allinstances = _allinstances
    82             allinstances = _allinstances
    83         return allinstances
    83         return allinstances
    84     
    84 
    85     def run(self, args):
    85     def run(self, args):
    86         """run the <command>_method on each argument (a list of application
    86         """run the <command>_method on each argument (a list of application
    87         identifiers)
    87         identifiers)
    88         """
    88         """
    89         if not args:
    89         if not args:
    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
   782 
   782 
   783 class ListCubesCommand(Command):
   783 class ListCubesCommand(Command):
   784     """list available componants, useful for bash completion."""
   784     """list available componants, useful for bash completion."""
   785     name = 'listcubes'
   785     name = 'listcubes'
   786     hidden = True
   786     hidden = True
   787     
   787 
   788     def run(self, args):
   788     def run(self, args):
   789         """run the command with its specific arguments"""
   789         """run the command with its specific arguments"""
   790         for cube in cwcfg.available_cubes():
   790         for cube in cwcfg.available_cubes():
   791             print cube
   791             print cube
   792 
   792 
   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