cwctl.py
changeset 1808 aa09e20dd8c0
parent 1477 b056a49c16dc
child 1898 39b37f90a8a4
equal deleted inserted replaced
1693:49075f57cf2c 1808:aa09e20dd8c0
     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
     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
     9 
     9 
       
    10 from logilab.common.clcommands import register_commands, pop_arg
       
    11 
    10 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
    12 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
    11 from cubicweb.cwconfig import CubicWebConfiguration, CONFIGURATIONS
    13 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
    12 from cubicweb.toolsutils import (Command, register_commands, main_run, 
    14 from cubicweb.toolsutils import Command, main_run,  rm, create_dir, confirm
    13                                  rm, create_dir, pop_arg, confirm)
    15 
    14     
       
    15 def wait_process_end(pid, maxtry=10, waittime=1):
    16 def wait_process_end(pid, maxtry=10, waittime=1):
    16     """wait for a process to actually die"""
    17     """wait for a process to actually die"""
    17     import signal
    18     import signal
    18     from time import sleep
    19     from time import sleep
    19     nbtry = 0
    20     nbtry = 0
    39     for fname in ('data', 'views', 'views.py'):
    40     for fname in ('data', 'views', 'views.py'):
    40         if exists(join(templdir, fname)):
    41         if exists(join(templdir, fname)):
    41             modes.append('web ui')
    42             modes.append('web ui')
    42             break
    43             break
    43     return modes
    44     return modes
    44     
    45 
    45     
    46 
    46 class ApplicationCommand(Command):
    47 class ApplicationCommand(Command):
    47     """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
    48     (0 meaning all registered applications)
    49     (0 meaning all registered applications)
    49     """
    50     """
    50     arguments = '[<application>...]'    
    51     arguments = '[<application>...]'
    51     options = (
    52     options = (
    52         ("force",
    53         ("force",
    53          {'short': 'f', 'action' : 'store_true',
    54          {'short': 'f', 'action' : 'store_true',
    54           'default': False,
    55           'default': False,
    55           'help': 'force command without asking confirmation',
    56           'help': 'force command without asking confirmation',
    56           }
    57           }
    57          ),
    58          ),
    58         )
    59         )
    59     actionverb = None
    60     actionverb = None
    60     
    61 
    61     def ordered_instances(self):
    62     def ordered_instances(self):
    62         """return instances in the order in which they should be started,
    63         """return instances in the order in which they should be started,
    63         considering $REGISTRY_DIR/startorder file if it exists (useful when
    64         considering $REGISTRY_DIR/startorder file if it exists (useful when
    64         some instances depends on another as external source
    65         some instances depends on another as external source
    65         """
    66         """
    66         regdir = CubicWebConfiguration.registry_dir()
    67         regdir = cwcfg.registry_dir()
    67         _allinstances = list_instances(regdir)
    68         _allinstances = list_instances(regdir)
    68         if isfile(join(regdir, 'startorder')):
    69         if isfile(join(regdir, 'startorder')):
    69             allinstances = []
    70             allinstances = []
    70             for line in file(join(regdir, 'startorder')):
    71             for line in file(join(regdir, 'startorder')):
    71                 line = line.strip()
    72                 line = line.strip()
    72                 if line and not line.startswith('#'):
    73                 if line and not line.startswith('#'):
    73                     try:
    74                     try:
    74                         _allinstances.remove(line)
    75                         _allinstances.remove(line)
    75                         allinstances.append(line)
    76                         allinstances.append(line)
    76                     except ValueError:
    77                     except ValueError:
    77                         print 'ERROR: startorder file contains unexistant instance %s' % line
    78                         print ('ERROR: startorder file contains unexistant '
       
    79                                'instance %s' % line)
    78             allinstances += _allinstances
    80             allinstances += _allinstances
    79         else:
    81         else:
    80             allinstances = _allinstances
    82             allinstances = _allinstances
    81         return allinstances
    83         return allinstances
    82     
    84 
    83     def run(self, args):
    85     def run(self, args):
    84         """run the <command>_method on each argument (a list of application
    86         """run the <command>_method on each argument (a list of application
    85         identifiers)
    87         identifiers)
    86         """
    88         """
    87         if not args:
    89         if not args:
    92                 # no force option
    94                 # no force option
    93                 askconfirm = False
    95                 askconfirm = False
    94         else:
    96         else:
    95             askconfirm = False
    97             askconfirm = False
    96         self.run_args(args, askconfirm)
    98         self.run_args(args, askconfirm)
    97         
    99 
    98     def run_args(self, args, askconfirm):
   100     def run_args(self, args, askconfirm):
    99         for appid in args:
   101         for appid in args:
   100             if askconfirm:
   102             if askconfirm:
   101                 print '*'*72
   103                 print '*'*72
   102                 if not confirm('%s application %r ?' % (self.name, appid)):
   104                 if not confirm('%s application %r ?' % (self.name, appid)):
   103                     continue
   105                     continue
   104             self.run_arg(appid)
   106             self.run_arg(appid)
   105             
   107 
   106     def run_arg(self, appid):
   108     def run_arg(self, appid):
   107         cmdmeth = getattr(self, '%s_application' % self.name)
   109         cmdmeth = getattr(self, '%s_application' % self.name)
   108         try:
   110         try:
   109             cmdmeth(appid)
   111             cmdmeth(appid)
   110         except (KeyboardInterrupt, SystemExit):
   112         except (KeyboardInterrupt, SystemExit):
   139                 status = system('%s %s' % (forkcmd, appid))
   141                 status = system('%s %s' % (forkcmd, appid))
   140                 if status:
   142                 if status:
   141                     sys.exit(status)
   143                     sys.exit(status)
   142             else:
   144             else:
   143                 self.run_arg(appid)
   145                 self.run_arg(appid)
   144     
   146 
   145 # base commands ###############################################################
   147 # base commands ###############################################################
   146 
   148 
   147 class ListCommand(Command):
   149 class ListCommand(Command):
   148     """List configurations, componants and applications.
   150     """List configurations, componants and applications.
   149 
   151 
   151     registered applications
   153     registered applications
   152     """
   154     """
   153     name = 'list'
   155     name = 'list'
   154     options = (
   156     options = (
   155         ('verbose',
   157         ('verbose',
   156          {'short': 'v', 'action' : 'store_true', 
   158          {'short': 'v', 'action' : 'store_true',
   157           'help': "display more information."}),        
   159           'help': "display more information."}),
   158         )
   160         )
   159     
   161 
   160     def run(self, args):
   162     def run(self, args):
   161         """run the command with its specific arguments"""
   163         """run the command with its specific arguments"""
   162         if args:
   164         if args:
   163             raise BadCommandUsage('Too much arguments')
   165             raise BadCommandUsage('Too much arguments')
   164         print 'CubicWeb version:', CubicWebConfiguration.cubicweb_version()
   166         print 'CubicWeb version:', cwcfg.cubicweb_version()
   165         print 'Detected mode:', CubicWebConfiguration.mode
   167         print 'Detected mode:', cwcfg.mode
   166         print
   168         print
   167         print 'Available configurations:'
   169         print 'Available configurations:'
   168         for config in CONFIGURATIONS:
   170         for config in CONFIGURATIONS:
   169             print '*', config.name
   171             print '*', config.name
   170             for line in config.__doc__.splitlines():
   172             for line in config.__doc__.splitlines():
   171                 line = line.strip()
   173                 line = line.strip()
   172                 if not line:
   174                 if not line:
   173                     continue
   175                     continue
   174                 print '   ', line
   176                 print '   ', line
   175         print 
   177         print
   176         cubesdirs = ', '.join(CubicWebConfiguration.cubes_search_path())
   178         try:
   177         try:
   179             cubesdir = pathsep.join(cwcfg.cubes_search_path())
   178             namesize = max(len(x) for x in CubicWebConfiguration.available_cubes())
   180             namesize = max(len(x) for x in cwcfg.available_cubes())
   179         except ConfigurationError, ex:
   181         except ConfigurationError, ex:
   180             print 'No cubes available:', ex
   182             print 'No cubes available:', ex
   181         except ValueError:
   183         except ValueError:
   182             print 'No cubes available in %s' % cubesdirs
   184             print 'No cubes available in %s' % cubesdir
   183         else:
   185         else:
   184             print 'Available cubes (%s):' % cubesdirs
   186             print 'Available cubes (%s):' % cubesdir
   185             for cube in CubicWebConfiguration.available_cubes():
   187             for cube in cwcfg.available_cubes():
   186                 if cube in ('CVS', '.svn', 'shared', '.hg'):
   188                 if cube in ('CVS', '.svn', 'shared', '.hg'):
   187                     continue
   189                     continue
   188                 try:
   190                 try:
   189                     tinfo = CubicWebConfiguration.cube_pkginfo(cube)
   191                     tinfo = cwcfg.cube_pkginfo(cube)
   190                     tversion = tinfo.version
   192                     tversion = tinfo.version
   191                 except ConfigurationError:
   193                 except ConfigurationError:
   192                     tinfo = None
   194                     tinfo = None
   193                     tversion = '[missing cube information]'
   195                     tversion = '[missing cube information]'
   194                 print '* %s %s' % (cube.ljust(namesize), tversion)
   196                 print '* %s %s' % (cube.ljust(namesize), tversion)
   199                         print '    '+ '    \n'.join(shortdesc.splitlines())
   201                         print '    '+ '    \n'.join(shortdesc.splitlines())
   200                     modes = detect_available_modes(CubicWebConfiguration.cube_dir(cube))
   202                     modes = detect_available_modes(CubicWebConfiguration.cube_dir(cube))
   201                     print '    available modes: %s' % ', '.join(modes)
   203                     print '    available modes: %s' % ', '.join(modes)
   202         print
   204         print
   203         try:
   205         try:
   204             regdir = CubicWebConfiguration.registry_dir()
   206             regdir = cwcfg.registry_dir()
   205         except ConfigurationError, ex:
   207         except ConfigurationError, ex:
   206             print 'No application available:', ex
   208             print 'No application available:', ex
   207             print
   209             print
   208             return
   210             return
   209         instances = list_instances(regdir)
   211         instances = list_instances(regdir)
   210         if instances:
   212         if instances:
   211             print 'Available applications (%s):' % regdir
   213             print 'Available applications (%s):' % regdir
   212             for appid in instances:
   214             for appid in instances:
   213                 modes = CubicWebConfiguration.possible_configurations(appid)
   215                 modes = cwcfg.possible_configurations(appid)
   214                 if not modes:
   216                 if not modes:
   215                     print '* %s (BROKEN application, no configuration found)' % appid
   217                     print '* %s (BROKEN application, no configuration found)' % appid
   216                     continue
   218                     continue
   217                 print '* %s (%s)' % (appid, ', '.join(modes))
   219                 print '* %s (%s)' % (appid, ', '.join(modes))
   218                 try:
   220                 try:
   219                     config = CubicWebConfiguration.config_for(appid, modes[0])
   221                     config = cwcfg.config_for(appid, modes[0])
   220                 except Exception, exc: 
   222                 except Exception, exc:
   221                     print '    (BROKEN application, %s)' % exc
   223                     print '    (BROKEN application, %s)' % exc
   222                     continue
   224                     continue
   223         else:
   225         else:
   224             print 'No application available in %s' % regdir
   226             print 'No application available in %s' % regdir
   225         print
   227         print
   257 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 \
   258 repository and the web server.',
   260 repository and the web server.',
   259           }
   261           }
   260          ),
   262          ),
   261         )
   263         )
   262     
   264 
   263     def run(self, args):
   265     def run(self, args):
   264         """run the command with its specific arguments"""
   266         """run the command with its specific arguments"""
   265         from logilab.common.textutils import get_csv
   267         from logilab.common.textutils import get_csv
   266         configname = self.config.config
   268         configname = self.config.config
   267         cubes = get_csv(pop_arg(args, 1))
   269         cubes = get_csv(pop_arg(args, 1))
   268         appid = pop_arg(args)
   270         appid = pop_arg(args)
   269         # get the configuration and helper
   271         # get the configuration and helper
   270         CubicWebConfiguration.creating = True
   272         cwcfg.creating = True
   271         config = CubicWebConfiguration.config_for(appid, configname)
   273         config = cwcfg.config_for(appid, configname)
   272         config.set_language = False
   274         config.set_language = False
   273         config.init_cubes(config.expand_cubes(cubes))
   275         config.init_cubes(config.expand_cubes(cubes))
   274         helper = self.config_helper(config)
   276         helper = self.config_helper(config)
   275         # check the cube exists
   277         # check the cube exists
   276         try:
   278         try:
   277             templdirs = [CubicWebConfiguration.cube_dir(cube)
   279             templdirs = [cwcfg.cube_dir(cube)
   278                          for cube in cubes]
   280                          for cube in cubes]
   279         except ConfigurationError, ex:
   281         except ConfigurationError, ex:
   280             print ex
   282             print ex
   281             print '\navailable cubes:',
   283             print '\navailable cubes:',
   282             print ', '.join(CubicWebConfiguration.available_cubes())
   284             print ', '.join(cwcfg.available_cubes())
   283             return
   285             return
   284         # create the registry directory for this application
   286         # create the registry directory for this application
   285         create_dir(config.apphome)
   287         create_dir(config.apphome)
   286         # load site_cubicweb from the cubes dir (if any)
   288         # load site_cubicweb from the cubes dir (if any)
   287         config.load_site_cubicweb()
   289         config.load_site_cubicweb()
   293         print
   295         print
   294         helper.bootstrap(cubes, self.config.config_level)
   296         helper.bootstrap(cubes, self.config.config_level)
   295         # write down configuration
   297         # write down configuration
   296         config.save()
   298         config.save()
   297         # handle i18n files structure
   299         # handle i18n files structure
   298         # XXX currently available languages are guessed from translations found
       
   299         # in the first cube given
   300         # in the first cube given
   300         from cubicweb.common import i18n
   301         from cubicweb.common import i18n
   301         langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
   302         langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
   302         errors = config.i18ncompile(langs)
   303         errors = config.i18ncompile(langs)
   303         if errors:
   304         if errors:
   320         print 'application %s (%s) created in %r' % (appid, configname,
   321         print 'application %s (%s) created in %r' % (appid, configname,
   321                                                      config.apphome)
   322                                                      config.apphome)
   322         print
   323         print
   323         helper.postcreate()
   324         helper.postcreate()
   324 
   325 
   325     
   326 
   326 class DeleteApplicationCommand(Command):
   327 class DeleteApplicationCommand(Command):
   327     """Delete an application. Will remove application's files and
   328     """Delete an application. Will remove application's files and
   328     unregister it.
   329     unregister it.
   329     """
   330     """
   330     name = 'delete'
   331     name = 'delete'
   331     arguments = '<application>'
   332     arguments = '<application>'
   332     
   333 
   333     options = ()
   334     options = ()
   334 
   335 
   335     def run(self, args):
   336     def run(self, args):
   336         """run the command with its specific arguments"""
   337         """run the command with its specific arguments"""
   337         appid = pop_arg(args, msg="No application specified !")
   338         appid = pop_arg(args, msg="No application specified !")
   338         configs = [CubicWebConfiguration.config_for(appid, configname)
   339         configs = [cwcfg.config_for(appid, configname)
   339                    for configname in CubicWebConfiguration.possible_configurations(appid)]
   340                    for configname in cwcfg.possible_configurations(appid)]
   340         if not configs:
   341         if not configs:
   341             raise ExecutionError('unable to guess configuration for %s' % appid)
   342             raise ExecutionError('unable to guess configuration for %s' % appid)
   342         for config in configs:
   343         for config in configs:
   343             helper = self.config_helper(config, required=False)
   344             helper = self.config_helper(config, required=False)
   344             if helper:
   345             if helper:
   358 
   359 
   359 # application commands ########################################################
   360 # application commands ########################################################
   360 
   361 
   361 class StartApplicationCommand(ApplicationCommand):
   362 class StartApplicationCommand(ApplicationCommand):
   362     """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.
   363     
   364 
   364     <application>...
   365     <application>...
   365       identifiers of the applications to start. If no application is
   366       identifiers of the applications to start. If no application is
   366       given, start them all.
   367       given, start them all.
   367     """
   368     """
   368     name = 'start'
   369     name = 'start'
   387         """start the application's server"""
   388         """start the application's server"""
   388         # use get() since start may be used from other commands (eg upgrade)
   389         # use get() since start may be used from other commands (eg upgrade)
   389         # without all options defined
   390         # without all options defined
   390         debug = self.get('debug')
   391         debug = self.get('debug')
   391         force = self.get('force')
   392         force = self.get('force')
   392         config = CubicWebConfiguration.config_for(appid)
   393         config = cwcfg.config_for(appid)
   393         if self.get('profile'):
   394         if self.get('profile'):
   394             config.global_set_option('profile', self.config.profile)
   395             config.global_set_option('profile', self.config.profile)
   395         helper = self.config_helper(config, cmdname='start')
   396         helper = self.config_helper(config, cmdname='start')
   396         pidf = config['pid-file']
   397         pidf = config['pid-file']
   397         if exists(pidf) and not force:
   398         if exists(pidf) and not force:
   411         return True
   412         return True
   412 
   413 
   413 
   414 
   414 class StopApplicationCommand(ApplicationCommand):
   415 class StopApplicationCommand(ApplicationCommand):
   415     """Stop the given applications.
   416     """Stop the given applications.
   416     
   417 
   417     <application>...
   418     <application>...
   418       identifiers of the applications to stop. If no application is
   419       identifiers of the applications to stop. If no application is
   419       given, stop them all.
   420       given, stop them all.
   420     """
   421     """
   421     name = 'stop'
   422     name = 'stop'
   422     actionverb = 'stopped'
   423     actionverb = 'stopped'
   423     
   424 
   424     def ordered_instances(self):
   425     def ordered_instances(self):
   425         instances = super(StopApplicationCommand, self).ordered_instances()
   426         instances = super(StopApplicationCommand, self).ordered_instances()
   426         instances.reverse()
   427         instances.reverse()
   427         return instances
   428         return instances
   428     
   429 
   429     def stop_application(self, appid):
   430     def stop_application(self, appid):
   430         """stop the application's server"""
   431         """stop the application's server"""
   431         config = CubicWebConfiguration.config_for(appid)
   432         config = cwcfg.config_for(appid)
   432         helper = self.config_helper(config, cmdname='stop')
   433         helper = self.config_helper(config, cmdname='stop')
   433         helper.poststop() # do this anyway
   434         helper.poststop() # do this anyway
   434         pidf = config['pid-file']
   435         pidf = config['pid-file']
   435         if not exists(pidf):
   436         if not exists(pidf):
   436             print >> sys.stderr, "%s doesn't exist." % pidf
   437             print >> sys.stderr, "%s doesn't exist." % pidf
   457             remove(pidf)
   458             remove(pidf)
   458         except OSError:
   459         except OSError:
   459             # already removed by twistd
   460             # already removed by twistd
   460             pass
   461             pass
   461         print 'application %s stopped' % appid
   462         print 'application %s stopped' % appid
   462     
   463 
   463 
   464 
   464 class RestartApplicationCommand(StartApplicationCommand,
   465 class RestartApplicationCommand(StartApplicationCommand,
   465                                 StopApplicationCommand):
   466                                 StopApplicationCommand):
   466     """Restart the given applications.
   467     """Restart the given applications.
   467     
   468 
   468     <application>...
   469     <application>...
   469       identifiers of the applications to restart. If no application is
   470       identifiers of the applications to restart. If no application is
   470       given, restart them all.
   471       given, restart them all.
   471     """
   472     """
   472     name = 'restart'
   473     name = 'restart'
   473     actionverb = 'restarted'
   474     actionverb = 'restarted'
   474 
   475 
   475     def run_args(self, args, askconfirm):
   476     def run_args(self, args, askconfirm):
   476         regdir = CubicWebConfiguration.registry_dir()
   477         regdir = cwcfg.registry_dir()
   477         if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
   478         if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
   478             # no specific startorder
   479             # no specific startorder
   479             super(RestartApplicationCommand, self).run_args(args, askconfirm)
   480             super(RestartApplicationCommand, self).run_args(args, askconfirm)
   480             return
   481             return
   481         print ('some specific start order is specified, will first stop all '
   482         print ('some specific start order is specified, will first stop all '
   494         forkcmd = ' '.join(forkcmd)
   495         forkcmd = ' '.join(forkcmd)
   495         for appid in reversed(args):
   496         for appid in reversed(args):
   496             status = system('%s %s' % (forkcmd, appid))
   497             status = system('%s %s' % (forkcmd, appid))
   497             if status:
   498             if status:
   498                 sys.exit(status)
   499                 sys.exit(status)
   499     
   500 
   500     def restart_application(self, appid):
   501     def restart_application(self, appid):
   501         self.stop_application(appid)
   502         self.stop_application(appid)
   502         if self.start_application(appid):
   503         if self.start_application(appid):
   503             print 'application %s %s' % (appid, self.actionverb)
   504             print 'application %s %s' % (appid, self.actionverb)
   504 
   505 
   505         
   506 
   506 class ReloadConfigurationCommand(RestartApplicationCommand):
   507 class ReloadConfigurationCommand(RestartApplicationCommand):
   507     """Reload the given applications. This command is equivalent to a
   508     """Reload the given applications. This command is equivalent to a
   508     restart for now.
   509     restart for now.
   509     
   510 
   510     <application>...
   511     <application>...
   511       identifiers of the applications to reload. If no application is
   512       identifiers of the applications to reload. If no application is
   512       given, reload them all.
   513       given, reload them all.
   513     """
   514     """
   514     name = 'reload'
   515     name = 'reload'
   515     
   516 
   516     def reload_application(self, appid):
   517     def reload_application(self, appid):
   517         self.restart_application(appid)
   518         self.restart_application(appid)
   518     
   519 
   519 
   520 
   520 class StatusCommand(ApplicationCommand):
   521 class StatusCommand(ApplicationCommand):
   521     """Display status information about the given applications.
   522     """Display status information about the given applications.
   522     
   523 
   523     <application>...
   524     <application>...
   524       identifiers of the applications to status. If no application is
   525       identifiers of the applications to status. If no application is
   525       given, get status information about all registered applications.
   526       given, get status information about all registered applications.
   526     """
   527     """
   527     name = 'status'
   528     name = 'status'
   528     options = ()
   529     options = ()
   529 
   530 
   530     def status_application(self, appid):
   531     @staticmethod
       
   532     def status_application(appid):
   531         """print running status information for an application"""
   533         """print running status information for an application"""
   532         for mode in CubicWebConfiguration.possible_configurations(appid):
   534         for mode in cwcfg.possible_configurations(appid):
   533             config = CubicWebConfiguration.config_for(appid, mode)
   535             config = cwcfg.config_for(appid, mode)
   534             print '[%s-%s]' % (appid, mode),
   536             print '[%s-%s]' % (appid, mode),
   535             try:
   537             try:
   536                 pidf = config['pid-file']
   538                 pidf = config['pid-file']
   537             except KeyError:
   539             except KeyError:
   538                 print 'buggy application, pid file not specified'
   540                 print 'buggy application, pid file not specified'
   572           'help': 'force migration from the indicated  version for the specified cube.'}),
   574           'help': 'force migration from the indicated  version for the specified cube.'}),
   573         ('force-cubicweb-version',
   575         ('force-cubicweb-version',
   574          {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
   576          {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z',
   575           'default': None,
   577           'default': None,
   576           'help': 'force migration from the indicated cubicweb version.'}),
   578           'help': 'force migration from the indicated cubicweb version.'}),
   577         
   579 
   578         ('fs-only',
   580         ('fs-only',
   579          {'short': 's', 'action' : 'store_true',
   581          {'short': 's', 'action' : 'store_true',
   580           'default': False,
   582           'default': False,
   581           'help': 'only upgrade files on the file system, not the database.'}),
   583           'help': 'only upgrade files on the file system, not the database.'}),
   582 
   584 
   583         ('nostartstop',
   585         ('nostartstop',
   584          {'short': 'n', 'action' : 'store_true',
   586          {'short': 'n', 'action' : 'store_true',
   585           'default': False,
   587           'default': False,
   586           '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.'}),
   587         
   589 
   588         ('verbosity',
   590         ('verbosity',
   589          {'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
   591          {'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
   590           'default': 1,
   592           'default': 1,
   591           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   593           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   592 for everything."}),
   594 for everything."}),
   593         
   595 
   594         ('backup-db',
   596         ('backup-db',
   595          {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
   597          {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
   596           'default': None,
   598           'default': None,
   597           'help': "Backup the application database before upgrade.\n"\
   599           'help': "Backup the application database before upgrade.\n"\
   598           "If the option is ommitted, confirmation will be ask.",
   600           "If the option is ommitted, confirmation will be ask.",
   609         )
   611         )
   610 
   612 
   611     def ordered_instances(self):
   613     def ordered_instances(self):
   612         # need this since mro return StopApplicationCommand implementation
   614         # need this since mro return StopApplicationCommand implementation
   613         return ApplicationCommand.ordered_instances(self)
   615         return ApplicationCommand.ordered_instances(self)
   614     
   616 
   615     def upgrade_application(self, appid):
   617     def upgrade_application(self, appid):
   616         from logilab.common.changelog import Version
   618         from logilab.common.changelog import Version
   617         config = CubicWebConfiguration.config_for(appid)
   619         config = cwcfg.config_for(appid)
   618         config.creating = True # notice we're not starting the server
   620         config.creating = True # notice we're not starting the server
   619         config.verbosity = self.config.verbosity
   621         config.verbosity = self.config.verbosity
   620         try:
   622         try:
   621             config.set_sources_mode(self.config.ext_sources or ('migration',))
   623             config.set_sources_mode(self.config.ext_sources or ('migration',))
   622         except AttributeError:
   624         except AttributeError:
   642             except KeyError:
   644             except KeyError:
   643                 config.error('no version information for %s' % cube)
   645                 config.error('no version information for %s' % cube)
   644                 continue
   646                 continue
   645             if installedversion > applversion:
   647             if installedversion > applversion:
   646                 toupgrade.append( (cube, applversion, installedversion) )
   648                 toupgrade.append( (cube, applversion, installedversion) )
   647         cubicwebversion = config.cubicweb_version()           
   649         cubicwebversion = config.cubicweb_version()
   648         if self.config.force_cubicweb_version:
   650         if self.config.force_cubicweb_version:
   649             applcubicwebversion = Version(self.config.force_cubicweb_version)
   651             applcubicwebversion = Version(self.config.force_cubicweb_version)
   650             vcconf['cubicweb'] = applcubicwebversion
   652             vcconf['cubicweb'] = applcubicwebversion
   651         else:
   653         else:
   652             applcubicwebversion = vcconf.get('cubicweb')
   654             applcubicwebversion = vcconf.get('cubicweb')
   656             print 'no software migration needed for application %s' % appid
   658             print 'no software migration needed for application %s' % appid
   657             return
   659             return
   658         for cube, fromversion, toversion in toupgrade:
   660         for cube, fromversion, toversion in toupgrade:
   659             print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
   661             print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
   660         # only stop once we're sure we have something to do
   662         # only stop once we're sure we have something to do
   661         if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
   663         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   662             self.stop_application(appid)
   664             self.stop_application(appid)
   663         # run cubicweb/componants migration scripts
   665         # run cubicweb/componants migration scripts
   664         mih.migrate(vcconf, reversed(toupgrade), self.config)
   666         mih.migrate(vcconf, reversed(toupgrade), self.config)
   665         # rewrite main configuration file
   667         # rewrite main configuration file
   666         mih.rewrite_configuration()
   668         mih.rewrite_configuration()
   667         # handle i18n upgrade:
   669         # handle i18n upgrade:
   668         # * install new languages
   670         # * install new languages
   669         # * recompile catalogs
   671         # * recompile catalogs
   670         # XXX currently available languages are guessed from translations found
       
   671         # in the first componant given
   672         # in the first componant given
   672         from cubicweb.common import i18n
   673         from cubicweb.common import i18n
   673         templdir = CubicWebConfiguration.cube_dir(config.cubes()[0])
   674         templdir = cwcfg.cube_dir(config.cubes()[0])
   674         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
   675         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
   675         errors = config.i18ncompile(langs)
   676         errors = config.i18ncompile(langs)
   676         if errors:
   677         if errors:
   677             print '\n'.join(errors)
   678             print '\n'.join(errors)
   678             if not confirm('error while compiling message catalogs, '
   679             if not confirm('error while compiling message catalogs, '
   681                 return
   682                 return
   682         mih.rewrite_vcconfiguration()
   683         mih.rewrite_vcconfiguration()
   683         mih.shutdown()
   684         mih.shutdown()
   684         print
   685         print
   685         print 'application migrated'
   686         print 'application migrated'
   686         if not (CubicWebConfiguration.mode == 'dev' or self.config.nostartstop):
   687         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   687             self.start_application(appid)
   688             self.start_application(appid)
   688         print
   689         print
   689 
   690 
   690 
   691 
   691 class ShellCommand(Command):
   692 class ShellCommand(Command):
   704          {'short': 'S', 'action' : 'store_true',
   705          {'short': 'S', 'action' : 'store_true',
   705           'default': False,
   706           'default': False,
   706           'help': 'only connect to the system source when the instance is '
   707           'help': 'only connect to the system source when the instance is '
   707           'using multiple sources. You can\'t use this option and the '
   708           'using multiple sources. You can\'t use this option and the '
   708           '--ext-sources option at the same time.'}),
   709           '--ext-sources option at the same time.'}),
   709         
   710 
   710         ('ext-sources',
   711         ('ext-sources',
   711          {'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
   712          {'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
   712           'default': None,
   713           'default': None,
   713           'help': "For multisources instances, specify to which sources the \
   714           'help': "For multisources instances, specify to which sources the \
   714 repository should connect to for upgrading. When unspecified or 'all' given, \
   715 repository should connect to for upgrading. When unspecified or 'all' given, \
   715 will connect to all defined sources. If 'migration' is given, appropriate \
   716 will connect to all defined sources. If 'migration' is given, appropriate \
   716 sources for migration will be automatically selected.",
   717 sources for migration will be automatically selected.",
   717           }),
   718           }),
   718         
   719 
   719         )
   720         )
   720     def run(self, args):
   721     def run(self, args):
   721         appid = pop_arg(args, 99, msg="No application specified !")
   722         appid = pop_arg(args, 99, msg="No application specified !")
   722         config = CubicWebConfiguration.config_for(appid)
   723         config = cwcfg.config_for(appid)
   723         if self.config.ext_sources:
   724         if self.config.ext_sources:
   724             assert not self.config.system_only
   725             assert not self.config.system_only
   725             sources = self.config.ext_sources
   726             sources = self.config.ext_sources
   726         elif self.config.system_only:
   727         elif self.config.system_only:
   727             sources = ('system',)
   728             sources = ('system',)
   731         mih = config.migration_handler()
   732         mih = config.migration_handler()
   732         if args:
   733         if args:
   733             mih.scripts_session(args)
   734             mih.scripts_session(args)
   734         else:
   735         else:
   735             mih.interactive_shell()
   736             mih.interactive_shell()
   736         mih.shutdown() 
   737         mih.shutdown()
   737 
   738 
   738 
   739 
   739 class RecompileApplicationCatalogsCommand(ApplicationCommand):
   740 class RecompileApplicationCatalogsCommand(ApplicationCommand):
   740     """Recompile i18n catalogs for applications.
   741     """Recompile i18n catalogs for applications.
   741     
   742 
   742     <application>...
   743     <application>...
   743       identifiers of the applications to consider. If no application is
   744       identifiers of the applications to consider. If no application is
   744       given, recompile for all registered applications.
   745       given, recompile for all registered applications.
   745     """
   746     """
   746     name = 'i18ncompile'
   747     name = 'i18ncompile'
   747     
   748 
   748     def i18ncompile_application(self, appid):
   749     @staticmethod
       
   750     def i18ncompile_application(appid):
   749         """recompile application's messages catalogs"""
   751         """recompile application's messages catalogs"""
   750         config = CubicWebConfiguration.config_for(appid)
   752         config = cwcfg.config_for(appid)
   751         try:
   753         try:
   752             config.bootstrap_cubes()
   754             config.bootstrap_cubes()
   753         except IOError, ex:
   755         except IOError, ex:
   754             import errno
   756             import errno
   755             if ex.errno != errno.ENOENT:
   757             if ex.errno != errno.ENOENT:
   769 
   771 
   770 class ListInstancesCommand(Command):
   772 class ListInstancesCommand(Command):
   771     """list available instances, useful for bash completion."""
   773     """list available instances, useful for bash completion."""
   772     name = 'listinstances'
   774     name = 'listinstances'
   773     hidden = True
   775     hidden = True
   774     
   776 
   775     def run(self, args):
   777     def run(self, args):
   776         """run the command with its specific arguments"""
   778         """run the command with its specific arguments"""
   777         regdir = CubicWebConfiguration.registry_dir()
   779         regdir = cwcfg.registry_dir()
   778         for appid in sorted(listdir(regdir)):
   780         for appid in sorted(listdir(regdir)):
   779             print appid
   781             print appid
   780 
   782 
   781 
   783 
   782 class ListCubesCommand(Command):
   784 class ListCubesCommand(Command):
   783     """list available componants, useful for bash completion."""
   785     """list available componants, useful for bash completion."""
   784     name = 'listcubes'
   786     name = 'listcubes'
   785     hidden = True
   787     hidden = True
   786     
   788 
   787     def run(self, args):
   789     def run(self, args):
   788         """run the command with its specific arguments"""
   790         """run the command with its specific arguments"""
   789         for cube in CubicWebConfiguration.available_cubes():
   791         for cube in cwcfg.available_cubes():
   790             print cube
   792             print cube
   791 
   793 
   792 register_commands((ListCommand,
   794 register_commands((ListCommand,
   793                    CreateApplicationCommand,
   795                    CreateApplicationCommand,
   794                    DeleteApplicationCommand,
   796                    DeleteApplicationCommand,
   801                    ShellCommand,
   803                    ShellCommand,
   802                    RecompileApplicationCatalogsCommand,
   804                    RecompileApplicationCatalogsCommand,
   803                    ListInstancesCommand, ListCubesCommand,
   805                    ListInstancesCommand, ListCubesCommand,
   804                    ))
   806                    ))
   805 
   807 
   806                 
   808 
   807 def run(args):
   809 def run(args):
   808     """command line tool"""
   810     """command line tool"""
   809     CubicWebConfiguration.load_cwctl_plugins()
   811     cwcfg.load_cwctl_plugins()
   810     main_run(args, __doc__)
   812     main_run(args, __doc__)
   811 
   813 
   812 if __name__ == '__main__':
   814 if __name__ == '__main__':
   813     run(sys.argv[1:])
   815     run(sys.argv[1:])