cwctl.py
changeset 2476 1294a6bdf3bf
parent 2473 490f88fb99b6
child 2489 37a747ad6fd4
equal deleted inserted replaced
2475:b6753521129d 2476:1294a6bdf3bf
     1 """%%prog %s [options] %s
     1 """%%prog %s [options] %s
     2 
     2 
     3 CubicWeb main applications controller.
     3 CubicWeb main instances controller.
     4 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     4 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     5 %s"""
     5 %s"""
     6 
     6 
     7 import sys
     7 import sys
     8 from os import remove, listdir, system, kill, getpgid, pathsep
     8 from os import remove, listdir, system, kill, getpgid, pathsep
    43             modes.append('web ui')
    43             modes.append('web ui')
    44             break
    44             break
    45     return modes
    45     return modes
    46 
    46 
    47 
    47 
    48 class ApplicationCommand(Command):
    48 class InstanceCommand(Command):
    49     """base class for command taking 0 to n application id as arguments
    49     """base class for command taking 0 to n instance id as arguments
    50     (0 meaning all registered applications)
    50     (0 meaning all registered instances)
    51     """
    51     """
    52     arguments = '[<application>...]'
    52     arguments = '[<instance>...]'
    53     options = (
    53     options = (
    54         ("force",
    54         ("force",
    55          {'short': 'f', 'action' : 'store_true',
    55          {'short': 'f', 'action' : 'store_true',
    56           'default': False,
    56           'default': False,
    57           'help': 'force command without asking confirmation',
    57           'help': 'force command without asking confirmation',
    82         else:
    82         else:
    83             allinstances = _allinstances
    83             allinstances = _allinstances
    84         return allinstances
    84         return allinstances
    85 
    85 
    86     def run(self, args):
    86     def run(self, args):
    87         """run the <command>_method on each argument (a list of application
    87         """run the <command>_method on each argument (a list of instance
    88         identifiers)
    88         identifiers)
    89         """
    89         """
    90         if not args:
    90         if not args:
    91             args = self.ordered_instances()
    91             args = self.ordered_instances()
    92             try:
    92             try:
   100 
   100 
   101     def run_args(self, args, askconfirm):
   101     def run_args(self, args, askconfirm):
   102         for appid in args:
   102         for appid in args:
   103             if askconfirm:
   103             if askconfirm:
   104                 print '*'*72
   104                 print '*'*72
   105                 if not confirm('%s application %r ?' % (self.name, appid)):
   105                 if not confirm('%s instance %r ?' % (self.name, appid)):
   106                     continue
   106                     continue
   107             self.run_arg(appid)
   107             self.run_arg(appid)
   108 
   108 
   109     def run_arg(self, appid):
   109     def run_arg(self, appid):
   110         cmdmeth = getattr(self, '%s_application' % self.name)
   110         cmdmeth = getattr(self, '%s_instance' % self.name)
   111         try:
   111         try:
   112             cmdmeth(appid)
   112             cmdmeth(appid)
   113         except (KeyboardInterrupt, SystemExit):
   113         except (KeyboardInterrupt, SystemExit):
   114             print >> sys.stderr, '%s aborted' % self.name
   114             print >> sys.stderr, '%s aborted' % self.name
   115             sys.exit(2) # specific error code
   115             sys.exit(2) # specific error code
   116         except (ExecutionError, ConfigurationError), ex:
   116         except (ExecutionError, ConfigurationError), ex:
   117             print >> sys.stderr, 'application %s not %s: %s' % (
   117             print >> sys.stderr, 'instance %s not %s: %s' % (
   118                 appid, self.actionverb, ex)
   118                 appid, self.actionverb, ex)
   119         except Exception, ex:
   119         except Exception, ex:
   120             import traceback
   120             import traceback
   121             traceback.print_exc()
   121             traceback.print_exc()
   122             print >> sys.stderr, 'application %s not %s: %s' % (
   122             print >> sys.stderr, 'instance %s not %s: %s' % (
   123                 appid, self.actionverb, ex)
   123                 appid, self.actionverb, ex)
   124 
   124 
   125 
   125 
   126 class ApplicationCommandFork(ApplicationCommand):
   126 class InstanceCommandFork(InstanceCommand):
   127     """Same as `ApplicationCommand`, but command is forked in a new environment
   127     """Same as `InstanceCommand`, but command is forked in a new environment
   128     for each argument
   128     for each argument
   129     """
   129     """
   130 
   130 
   131     def run_args(self, args, askconfirm):
   131     def run_args(self, args, askconfirm):
   132         if len(args) > 1:
   132         if len(args) > 1:
   134         else:
   134         else:
   135             forkcmd = None
   135             forkcmd = None
   136         for appid in args:
   136         for appid in args:
   137             if askconfirm:
   137             if askconfirm:
   138                 print '*'*72
   138                 print '*'*72
   139                 if not confirm('%s application %r ?' % (self.name, appid)):
   139                 if not confirm('%s instance %r ?' % (self.name, appid)):
   140                     continue
   140                     continue
   141             if forkcmd:
   141             if forkcmd:
   142                 status = system('%s %s' % (forkcmd, appid))
   142                 status = system('%s %s' % (forkcmd, appid))
   143                 if status:
   143                 if status:
   144                     print '%s exited with status %s' % (forkcmd, status)
   144                     print '%s exited with status %s' % (forkcmd, status)
   146                 self.run_arg(appid)
   146                 self.run_arg(appid)
   147 
   147 
   148 # base commands ###############################################################
   148 # base commands ###############################################################
   149 
   149 
   150 class ListCommand(Command):
   150 class ListCommand(Command):
   151     """List configurations, componants and applications.
   151     """List configurations, cubes and instances.
   152 
   152 
   153     list available configurations, installed web and server componants, and
   153     list available configurations, installed cubes, and registered instances
   154     registered applications
       
   155     """
   154     """
   156     name = 'list'
   155     name = 'list'
   157     options = (
   156     options = (
   158         ('verbose',
   157         ('verbose',
   159          {'short': 'v', 'action' : 'store_true',
   158          {'short': 'v', 'action' : 'store_true',
   204                     print '    available modes: %s' % ', '.join(modes)
   203                     print '    available modes: %s' % ', '.join(modes)
   205         print
   204         print
   206         try:
   205         try:
   207             regdir = cwcfg.registry_dir()
   206             regdir = cwcfg.registry_dir()
   208         except ConfigurationError, ex:
   207         except ConfigurationError, ex:
   209             print 'No application available:', ex
   208             print 'No instance available:', ex
   210             print
   209             print
   211             return
   210             return
   212         instances = list_instances(regdir)
   211         instances = list_instances(regdir)
   213         if instances:
   212         if instances:
   214             print 'Available applications (%s):' % regdir
   213             print 'Available instances (%s):' % regdir
   215             for appid in instances:
   214             for appid in instances:
   216                 modes = cwcfg.possible_configurations(appid)
   215                 modes = cwcfg.possible_configurations(appid)
   217                 if not modes:
   216                 if not modes:
   218                     print '* %s (BROKEN application, no configuration found)' % appid
   217                     print '* %s (BROKEN instance, no configuration found)' % appid
   219                     continue
   218                     continue
   220                 print '* %s (%s)' % (appid, ', '.join(modes))
   219                 print '* %s (%s)' % (appid, ', '.join(modes))
   221                 try:
   220                 try:
   222                     config = cwcfg.config_for(appid, modes[0])
   221                     config = cwcfg.config_for(appid, modes[0])
   223                 except Exception, exc:
   222                 except Exception, exc:
   224                     print '    (BROKEN application, %s)' % exc
   223                     print '    (BROKEN instance, %s)' % exc
   225                     continue
   224                     continue
   226         else:
   225         else:
   227             print 'No application available in %s' % regdir
   226             print 'No instance available in %s' % regdir
   228         print
   227         print
   229 
   228 
   230 
   229 
   231 class CreateApplicationCommand(Command):
   230 class CreateInstanceCommand(Command):
   232     """Create an application from a cube. This is an unified
   231     """Create an instance from a cube. This is an unified
   233     command which can handle web / server / all-in-one installation
   232     command which can handle web / server / all-in-one installation
   234     according to available parts of the software library and of the
   233     according to available parts of the software library and of the
   235     desired cube.
   234     desired cube.
   236 
   235 
   237     <cube>
   236     <cube>
   238       the name of cube to use (list available cube names using
   237       the name of cube to use (list available cube names using
   239       the "list" command). You can use several cubes by separating
   238       the "list" command). You can use several cubes by separating
   240       them using comma (e.g. 'jpl,eemail')
   239       them using comma (e.g. 'jpl,eemail')
   241     <application>
   240     <instance>
   242       an identifier for the application to create
   241       an identifier for the instance to create
   243     """
   242     """
   244     name = 'create'
   243     name = 'create'
   245     arguments = '<cube> <application>'
   244     arguments = '<cube> <instance>'
   246     options = (
   245     options = (
   247         ("config-level",
   246         ("config-level",
   248          {'short': 'l', 'type' : 'int', 'metavar': '<level>',
   247          {'short': 'l', 'type' : 'int', 'metavar': '<level>',
   249           'default': 0,
   248           'default': 0,
   250           'help': 'configuration level (0..2): 0 will ask for essential \
   249           'help': 'configuration level (0..2): 0 will ask for essential \
   253          ),
   252          ),
   254         ("config",
   253         ("config",
   255          {'short': 'c', 'type' : 'choice', 'metavar': '<install type>',
   254          {'short': 'c', 'type' : 'choice', 'metavar': '<install type>',
   256           'choices': ('all-in-one', 'repository', 'twisted'),
   255           'choices': ('all-in-one', 'repository', 'twisted'),
   257           'default': 'all-in-one',
   256           'default': 'all-in-one',
   258           'help': 'installation type, telling which part of an application \
   257           'help': 'installation type, telling which part of an instance \
   259 should be installed. You can list available configurations using the "list" \
   258 should be installed. You can list available configurations using the "list" \
   260 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 \
   261 repository and the web server.',
   260 repository and the web server.',
   262           }
   261           }
   263          ),
   262          ),
   283         except ConfigurationError, ex:
   282         except ConfigurationError, ex:
   284             print ex
   283             print ex
   285             print '\navailable cubes:',
   284             print '\navailable cubes:',
   286             print ', '.join(cwcfg.available_cubes())
   285             print ', '.join(cwcfg.available_cubes())
   287             return
   286             return
   288         # create the registry directory for this application
   287         # create the registry directory for this instance
   289         print '\n'+underline_title('Creating the application %s' % appid)
   288         print '\n'+underline_title('Creating the instance %s' % appid)
   290         create_dir(config.apphome)
   289         create_dir(config.apphome)
   291         # load site_cubicweb from the cubes dir (if any)
   290         # load site_cubicweb from the cubes dir (if any)
   292         config.load_site_cubicweb()
   291         config.load_site_cubicweb()
   293         # cubicweb-ctl configuration
   292         # cubicweb-ctl configuration
   294         print '\n'+underline_title('Configuring the application (%s.conf)' % configname)
   293         print '\n'+underline_title('Configuring the instance (%s.conf)' % configname)
   295         config.input_config('main', self.config.config_level)
   294         config.input_config('main', self.config.config_level)
   296         # configuration'specific stuff
   295         # configuration'specific stuff
   297         print
   296         print
   298         helper.bootstrap(cubes, self.config.config_level)
   297         helper.bootstrap(cubes, self.config.config_level)
   299         # write down configuration
   298         # write down configuration
   309             print '\n'.join(errors)
   308             print '\n'.join(errors)
   310             if not confirm('error while compiling message catalogs, '
   309             if not confirm('error while compiling message catalogs, '
   311                            'continue anyway ?'):
   310                            'continue anyway ?'):
   312                 print 'creation not completed'
   311                 print 'creation not completed'
   313                 return
   312                 return
   314         # create the additional data directory for this application
   313         # create the additional data directory for this instance
   315         if config.appdatahome != config.apphome: # true in dev mode
   314         if config.appdatahome != config.apphome: # true in dev mode
   316             create_dir(config.appdatahome)
   315             create_dir(config.appdatahome)
   317         if config['uid']:
   316         if config['uid']:
   318             from logilab.common.shellutils import chown
   317             from logilab.common.shellutils import chown
   319             # this directory should be owned by the uid of the server process
   318             # this directory should be owned by the uid of the server process
   321             chown(config.appdatahome, config['uid'])
   320             chown(config.appdatahome, config['uid'])
   322         print '\n-> creation done for %r.\n' % config.apphome
   321         print '\n-> creation done for %r.\n' % config.apphome
   323         helper.postcreate()
   322         helper.postcreate()
   324 
   323 
   325 
   324 
   326 class DeleteApplicationCommand(Command):
   325 class DeleteInstanceCommand(Command):
   327     """Delete an application. Will remove application's files and
   326     """Delete an instance. Will remove instance's files and
   328     unregister it.
   327     unregister it.
   329     """
   328     """
   330     name = 'delete'
   329     name = 'delete'
   331     arguments = '<application>'
   330     arguments = '<instance>'
   332 
   331 
   333     options = ()
   332     options = ()
   334 
   333 
   335     def run(self, args):
   334     def run(self, args):
   336         """run the command with its specific arguments"""
   335         """run the command with its specific arguments"""
   337         appid = pop_arg(args, msg="No application specified !")
   336         appid = pop_arg(args, msg="No instance specified !")
   338         configs = [cwcfg.config_for(appid, configname)
   337         configs = [cwcfg.config_for(appid, configname)
   339                    for configname in cwcfg.possible_configurations(appid)]
   338                    for configname in cwcfg.possible_configurations(appid)]
   340         if not configs:
   339         if not configs:
   341             raise ExecutionError('unable to guess configuration for %s' % appid)
   340             raise ExecutionError('unable to guess configuration for %s' % appid)
   342         for config in configs:
   341         for config in configs:
   351         except OSError, ex:
   350         except OSError, ex:
   352             import errno
   351             import errno
   353             if ex.errno != errno.ENOENT:
   352             if ex.errno != errno.ENOENT:
   354                 raise
   353                 raise
   355         confignames = ', '.join([config.name for config in configs])
   354         confignames = ', '.join([config.name for config in configs])
   356         print 'application %s (%s) deleted' % (appid, confignames)
   355         print 'instance %s (%s) deleted' % (appid, confignames)
   357 
   356 
   358 
   357 
   359 # application commands ########################################################
   358 # instance commands ########################################################
   360 
   359 
   361 class StartApplicationCommand(ApplicationCommand):
   360 class StartInstanceCommand(InstanceCommand):
   362     """Start the given applications. If no application is given, start them all.
   361     """Start the given instances. If no instance is given, start them all.
   363 
   362 
   364     <application>...
   363     <instance>...
   365       identifiers of the applications to start. If no application is
   364       identifiers of the instances to start. If no instance is
   366       given, start them all.
   365       given, start them all.
   367     """
   366     """
   368     name = 'start'
   367     name = 'start'
   369     actionverb = 'started'
   368     actionverb = 'started'
   370     options = (
   369     options = (
   372          {'short': 'D', 'action' : 'store_true',
   371          {'short': 'D', 'action' : 'store_true',
   373           'help': 'start server in debug mode.'}),
   372           'help': 'start server in debug mode.'}),
   374         ("force",
   373         ("force",
   375          {'short': 'f', 'action' : 'store_true',
   374          {'short': 'f', 'action' : 'store_true',
   376           'default': False,
   375           'default': False,
   377           'help': 'start the application even if it seems to be already \
   376           'help': 'start the instance even if it seems to be already \
   378 running.'}),
   377 running.'}),
   379         ('profile',
   378         ('profile',
   380          {'short': 'P', 'type' : 'string', 'metavar': '<stat file>',
   379          {'short': 'P', 'type' : 'string', 'metavar': '<stat file>',
   381           'default': None,
   380           'default': None,
   382           'help': 'profile code and use the specified file to store stats',
   381           'help': 'profile code and use the specified file to store stats',
   383           }),
   382           }),
   384         )
   383         )
   385 
   384 
   386     def start_application(self, appid):
   385     def start_instance(self, appid):
   387         """start the application's server"""
   386         """start the instance's server"""
   388         # use get() since start may be used from other commands (eg upgrade)
   387         # use get() since start may be used from other commands (eg upgrade)
   389         # without all options defined
   388         # without all options defined
   390         debug = self.get('debug')
   389         debug = self.get('debug')
   391         force = self.get('force')
   390         force = self.get('force')
   392         config = cwcfg.config_for(appid)
   391         config = cwcfg.config_for(appid)
   401         command = helper.start_command(config, debug)
   400         command = helper.start_command(config, debug)
   402         if debug:
   401         if debug:
   403             print "starting server with command :"
   402             print "starting server with command :"
   404             print command
   403             print command
   405         if system(command):
   404         if system(command):
   406             print 'an error occured while starting the application, not started'
   405             print 'an error occured while starting the instance, not started'
   407             print
   406             print
   408             return False
   407             return False
   409         if not debug:
   408         if not debug:
   410             print 'application %s started' % appid
   409             print 'instance %s started' % appid
   411         return True
   410         return True
   412 
   411 
   413 
   412 
   414 class StopApplicationCommand(ApplicationCommand):
   413 class StopInstanceCommand(InstanceCommand):
   415     """Stop the given applications.
   414     """Stop the given instances.
   416 
   415 
   417     <application>...
   416     <instance>...
   418       identifiers of the applications to stop. If no application is
   417       identifiers of the instances to stop. If no instance is
   419       given, stop them all.
   418       given, stop them all.
   420     """
   419     """
   421     name = 'stop'
   420     name = 'stop'
   422     actionverb = 'stopped'
   421     actionverb = 'stopped'
   423 
   422 
   424     def ordered_instances(self):
   423     def ordered_instances(self):
   425         instances = super(StopApplicationCommand, self).ordered_instances()
   424         instances = super(StopInstanceCommand, self).ordered_instances()
   426         instances.reverse()
   425         instances.reverse()
   427         return instances
   426         return instances
   428 
   427 
   429     def stop_application(self, appid):
   428     def stop_instance(self, appid):
   430         """stop the application's server"""
   429         """stop the instance's server"""
   431         config = cwcfg.config_for(appid)
   430         config = cwcfg.config_for(appid)
   432         helper = self.config_helper(config, cmdname='stop')
   431         helper = self.config_helper(config, cmdname='stop')
   433         helper.poststop() # do this anyway
   432         helper.poststop() # do this anyway
   434         pidf = config['pid-file']
   433         pidf = config['pid-file']
   435         if not exists(pidf):
   434         if not exists(pidf):
   456         try:
   455         try:
   457             remove(pidf)
   456             remove(pidf)
   458         except OSError:
   457         except OSError:
   459             # already removed by twistd
   458             # already removed by twistd
   460             pass
   459             pass
   461         print 'application %s stopped' % appid
   460         print 'instance %s stopped' % appid
   462 
   461 
   463 
   462 
   464 class RestartApplicationCommand(StartApplicationCommand,
   463 class RestartInstanceCommand(StartInstanceCommand,
   465                                 StopApplicationCommand):
   464                                 StopInstanceCommand):
   466     """Restart the given applications.
   465     """Restart the given instances.
   467 
   466 
   468     <application>...
   467     <instance>...
   469       identifiers of the applications to restart. If no application is
   468       identifiers of the instances to restart. If no instance is
   470       given, restart them all.
   469       given, restart them all.
   471     """
   470     """
   472     name = 'restart'
   471     name = 'restart'
   473     actionverb = 'restarted'
   472     actionverb = 'restarted'
   474 
   473 
   475     def run_args(self, args, askconfirm):
   474     def run_args(self, args, askconfirm):
   476         regdir = cwcfg.registry_dir()
   475         regdir = cwcfg.registry_dir()
   477         if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
   476         if not isfile(join(regdir, 'startorder')) or len(args) <= 1:
   478             # no specific startorder
   477             # no specific startorder
   479             super(RestartApplicationCommand, self).run_args(args, askconfirm)
   478             super(RestartInstanceCommand, self).run_args(args, askconfirm)
   480             return
   479             return
   481         print ('some specific start order is specified, will first stop all '
   480         print ('some specific start order is specified, will first stop all '
   482                'applications then restart them.')
   481                'instances then restart them.')
   483         # get instances in startorder
   482         # get instances in startorder
   484         stopped = []
   483         stopped = []
   485         for appid in args:
   484         for appid in args:
   486             if askconfirm:
   485             if askconfirm:
   487                 print '*'*72
   486                 print '*'*72
   488                 if not confirm('%s application %r ?' % (self.name, appid)):
   487                 if not confirm('%s instance %r ?' % (self.name, appid)):
   489                     continue
   488                     continue
   490             self.stop_application(appid)
   489             self.stop_instance(appid)
   491             stopped.append(appid)
   490             stopped.append(appid)
   492         forkcmd = [w for w in sys.argv if not w in args]
   491         forkcmd = [w for w in sys.argv if not w in args]
   493         forkcmd[1] = 'start'
   492         forkcmd[1] = 'start'
   494         forkcmd = ' '.join(forkcmd)
   493         forkcmd = ' '.join(forkcmd)
   495         for appid in reversed(args):
   494         for appid in reversed(args):
   496             status = system('%s %s' % (forkcmd, appid))
   495             status = system('%s %s' % (forkcmd, appid))
   497             if status:
   496             if status:
   498                 sys.exit(status)
   497                 sys.exit(status)
   499 
   498 
   500     def restart_application(self, appid):
   499     def restart_instance(self, appid):
   501         self.stop_application(appid)
   500         self.stop_instance(appid)
   502         if self.start_application(appid):
   501         if self.start_instance(appid):
   503             print 'application %s %s' % (appid, self.actionverb)
   502             print 'instance %s %s' % (appid, self.actionverb)
   504 
   503 
   505 
   504 
   506 class ReloadConfigurationCommand(RestartApplicationCommand):
   505 class ReloadConfigurationCommand(RestartInstanceCommand):
   507     """Reload the given applications. This command is equivalent to a
   506     """Reload the given instances. This command is equivalent to a
   508     restart for now.
   507     restart for now.
   509 
   508 
   510     <application>...
   509     <instance>...
   511       identifiers of the applications to reload. If no application is
   510       identifiers of the instances to reload. If no instance is
   512       given, reload them all.
   511       given, reload them all.
   513     """
   512     """
   514     name = 'reload'
   513     name = 'reload'
   515 
   514 
   516     def reload_application(self, appid):
   515     def reload_instance(self, appid):
   517         self.restart_application(appid)
   516         self.restart_instance(appid)
   518 
   517 
   519 
   518 
   520 class StatusCommand(ApplicationCommand):
   519 class StatusCommand(InstanceCommand):
   521     """Display status information about the given applications.
   520     """Display status information about the given instances.
   522 
   521 
   523     <application>...
   522     <instance>...
   524       identifiers of the applications to status. If no application is
   523       identifiers of the instances to status. If no instance is
   525       given, get status information about all registered applications.
   524       given, get status information about all registered instances.
   526     """
   525     """
   527     name = 'status'
   526     name = 'status'
   528     options = ()
   527     options = ()
   529 
   528 
   530     @staticmethod
   529     @staticmethod
   531     def status_application(appid):
   530     def status_instance(appid):
   532         """print running status information for an application"""
   531         """print running status information for an instance"""
   533         for mode in cwcfg.possible_configurations(appid):
   532         for mode in cwcfg.possible_configurations(appid):
   534             config = cwcfg.config_for(appid, mode)
   533             config = cwcfg.config_for(appid, mode)
   535             print '[%s-%s]' % (appid, mode),
   534             print '[%s-%s]' % (appid, mode),
   536             try:
   535             try:
   537                 pidf = config['pid-file']
   536                 pidf = config['pid-file']
   538             except KeyError:
   537             except KeyError:
   539                 print 'buggy application, pid file not specified'
   538                 print 'buggy instance, pid file not specified'
   540                 continue
   539                 continue
   541             if not exists(pidf):
   540             if not exists(pidf):
   542                 print "doesn't seem to be running"
   541                 print "doesn't seem to be running"
   543                 continue
   542                 continue
   544             pid = int(open(pidf).read().strip())
   543             pid = int(open(pidf).read().strip())
   549                 print "should be running with pid %s but the process can not be found" % pid
   548                 print "should be running with pid %s but the process can not be found" % pid
   550                 continue
   549                 continue
   551             print "running with pid %s" % (pid)
   550             print "running with pid %s" % (pid)
   552 
   551 
   553 
   552 
   554 class UpgradeApplicationCommand(ApplicationCommandFork,
   553 class UpgradeInstanceCommand(InstanceCommandFork,
   555                                 StartApplicationCommand,
   554                                 StartInstanceCommand,
   556                                 StopApplicationCommand):
   555                                 StopInstanceCommand):
   557     """Upgrade an application after cubicweb and/or component(s) upgrade.
   556     """Upgrade an instance after cubicweb and/or component(s) upgrade.
   558 
   557 
   559     For repository update, you will be prompted for a login / password to use
   558     For repository update, you will be prompted for a login / password to use
   560     to connect to the system database.  For some upgrades, the given user
   559     to connect to the system database.  For some upgrades, the given user
   561     should have create or alter table permissions.
   560     should have create or alter table permissions.
   562 
   561 
   563     <application>...
   562     <instance>...
   564       identifiers of the applications to upgrade. If no application is
   563       identifiers of the instances to upgrade. If no instance is
   565       given, upgrade them all.
   564       given, upgrade them all.
   566     """
   565     """
   567     name = 'upgrade'
   566     name = 'upgrade'
   568     actionverb = 'upgraded'
   567     actionverb = 'upgraded'
   569     options = ApplicationCommand.options + (
   568     options = InstanceCommand.options + (
   570         ('force-componant-version',
   569         ('force-componant-version',
   571          {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z',
   570          {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z',
   572           'default': None,
   571           'default': None,
   573           'help': 'force migration from the indicated  version for the specified cube.'}),
   572           'help': 'force migration from the indicated  version for the specified cube.'}),
   574         ('force-cubicweb-version',
   573         ('force-cubicweb-version',
   582           'help': 'only upgrade files on the file system, not the database.'}),
   581           'help': 'only upgrade files on the file system, not the database.'}),
   583 
   582 
   584         ('nostartstop',
   583         ('nostartstop',
   585          {'short': 'n', 'action' : 'store_true',
   584          {'short': 'n', 'action' : 'store_true',
   586           'default': False,
   585           'default': False,
   587           'help': 'don\'t try to stop application before migration and to restart it after.'}),
   586           'help': 'don\'t try to stop instance before migration and to restart it after.'}),
   588 
   587 
   589         ('verbosity',
   588         ('verbosity',
   590          {'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
   589          {'short': 'v', 'type' : 'int', 'metavar': '<0..2>',
   591           'default': 1,
   590           'default': 1,
   592           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   591           'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \
   593 for everything."}),
   592 for everything."}),
   594 
   593 
   595         ('backup-db',
   594         ('backup-db',
   596          {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
   595          {'short': 'b', 'type' : 'yn', 'metavar': '<y or n>',
   597           'default': None,
   596           'default': None,
   598           'help': "Backup the application database before upgrade.\n"\
   597           'help': "Backup the instance database before upgrade.\n"\
   599           "If the option is ommitted, confirmation will be ask.",
   598           "If the option is ommitted, confirmation will be ask.",
   600           }),
   599           }),
   601 
   600 
   602         ('ext-sources',
   601         ('ext-sources',
   603          {'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
   602          {'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
   608 (recommended). If 'all' is given, will connect to all defined sources.",
   607 (recommended). If 'all' is given, will connect to all defined sources.",
   609           }),
   608           }),
   610         )
   609         )
   611 
   610 
   612     def ordered_instances(self):
   611     def ordered_instances(self):
   613         # need this since mro return StopApplicationCommand implementation
   612         # need this since mro return StopInstanceCommand implementation
   614         return ApplicationCommand.ordered_instances(self)
   613         return InstanceCommand.ordered_instances(self)
   615 
   614 
   616     def upgrade_application(self, appid):
   615     def upgrade_instance(self, appid):
   617         from logilab.common.changelog import Version
   616         from logilab.common.changelog import Version
   618         config = cwcfg.config_for(appid)
   617         config = cwcfg.config_for(appid)
   619         config.repairing = True # notice we're not starting the server
   618         config.repairing = True # notice we're not starting the server
   620         config.verbosity = self.config.verbosity
   619         config.verbosity = self.config.verbosity
   621         try:
   620         try:
   622             config.set_sources_mode(self.config.ext_sources or ('migration',))
   621             config.set_sources_mode(self.config.ext_sources or ('migration',))
   623         except AttributeError:
   622         except AttributeError:
   624             # not a server config
   623             # not a server config
   625             pass
   624             pass
   626         # get application and installed versions for the server and the componants
   625         # get instance and installed versions for the server and the componants
   627         print 'getting versions configuration from the repository...'
   626         print 'getting versions configuration from the repository...'
   628         mih = config.migration_handler()
   627         mih = config.migration_handler()
   629         repo = mih.repo_connect()
   628         repo = mih.repo_connect()
   630         vcconf = repo.get_versions()
   629         vcconf = repo.get_versions()
   631         print 'done'
   630         print 'done'
   652         else:
   651         else:
   653             applcubicwebversion = vcconf.get('cubicweb')
   652             applcubicwebversion = vcconf.get('cubicweb')
   654         if cubicwebversion > applcubicwebversion:
   653         if cubicwebversion > applcubicwebversion:
   655             toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
   654             toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion))
   656         if not self.config.fs_only and not toupgrade:
   655         if not self.config.fs_only and not toupgrade:
   657             print 'no software migration needed for application %s' % appid
   656             print 'no software migration needed for instance %s' % appid
   658             return
   657             return
   659         for cube, fromversion, toversion in toupgrade:
   658         for cube, fromversion, toversion in toupgrade:
   660             print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
   659             print '**** %s migration %s -> %s' % (cube, fromversion, toversion)
   661         # only stop once we're sure we have something to do
   660         # only stop once we're sure we have something to do
   662         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   661         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   663             self.stop_application(appid)
   662             self.stop_instance(appid)
   664         # run cubicweb/componants migration scripts
   663         # run cubicweb/componants migration scripts
   665         mih.migrate(vcconf, reversed(toupgrade), self.config)
   664         mih.migrate(vcconf, reversed(toupgrade), self.config)
   666         # rewrite main configuration file
   665         # rewrite main configuration file
   667         mih.rewrite_configuration()
   666         mih.rewrite_configuration()
   668         # handle i18n upgrade:
   667         # handle i18n upgrade:
   679                            'continue anyway ?'):
   678                            'continue anyway ?'):
   680                 print 'migration not completed'
   679                 print 'migration not completed'
   681                 return
   680                 return
   682         mih.shutdown()
   681         mih.shutdown()
   683         print
   682         print
   684         print 'application migrated'
   683         print 'instance migrated'
   685         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   684         if not (cwcfg.mode == 'dev' or self.config.nostartstop):
   686             self.start_application(appid)
   685             self.start_instance(appid)
   687         print
   686         print
   688 
   687 
   689 
   688 
   690 class ShellCommand(Command):
   689 class ShellCommand(Command):
   691     """Run an interactive migration shell. This is a python shell with
   690     """Run an interactive migration shell. This is a python shell with
   692     enhanced migration commands predefined in the namespace. An additional
   691     enhanced migration commands predefined in the namespace. An additional
   693     argument may be given corresponding to a file containing commands to
   692     argument may be given corresponding to a file containing commands to
   694     execute in batch mode.
   693     execute in batch mode.
   695 
   694 
   696     <application>
   695     <instance>
   697       the identifier of the application to connect.
   696       the identifier of the instance to connect.
   698     """
   697     """
   699     name = 'shell'
   698     name = 'shell'
   700     arguments = '<application> [batch command file]'
   699     arguments = '<instance> [batch command file]'
   701     options = (
   700     options = (
   702         ('system-only',
   701         ('system-only',
   703          {'short': 'S', 'action' : 'store_true',
   702          {'short': 'S', 'action' : 'store_true',
   704           'default': False,
   703           'default': False,
   705           'help': 'only connect to the system source when the instance is '
   704           'help': 'only connect to the system source when the instance is '
   715 sources for migration will be automatically selected.",
   714 sources for migration will be automatically selected.",
   716           }),
   715           }),
   717 
   716 
   718         )
   717         )
   719     def run(self, args):
   718     def run(self, args):
   720         appid = pop_arg(args, 99, msg="No application specified !")
   719         appid = pop_arg(args, 99, msg="No instance specified !")
   721         config = cwcfg.config_for(appid)
   720         config = cwcfg.config_for(appid)
   722         if self.config.ext_sources:
   721         if self.config.ext_sources:
   723             assert not self.config.system_only
   722             assert not self.config.system_only
   724             sources = self.config.ext_sources
   723             sources = self.config.ext_sources
   725         elif self.config.system_only:
   724         elif self.config.system_only:
   734         else:
   733         else:
   735             mih.interactive_shell()
   734             mih.interactive_shell()
   736         mih.shutdown()
   735         mih.shutdown()
   737 
   736 
   738 
   737 
   739 class RecompileApplicationCatalogsCommand(ApplicationCommand):
   738 class RecompileInstanceCatalogsCommand(InstanceCommand):
   740     """Recompile i18n catalogs for applications.
   739     """Recompile i18n catalogs for instances.
   741 
   740 
   742     <application>...
   741     <instance>...
   743       identifiers of the applications to consider. If no application is
   742       identifiers of the instances to consider. If no instance is
   744       given, recompile for all registered applications.
   743       given, recompile for all registered instances.
   745     """
   744     """
   746     name = 'i18ninstance'
   745     name = 'i18ninstance'
   747 
   746 
   748     @staticmethod
   747     @staticmethod
   749     def i18ninstance_application(appid):
   748     def i18ninstance_instance(appid):
   750         """recompile application's messages catalogs"""
   749         """recompile instance's messages catalogs"""
   751         config = cwcfg.config_for(appid)
   750         config = cwcfg.config_for(appid)
   752         try:
   751         try:
   753             config.bootstrap_cubes()
   752             config.bootstrap_cubes()
   754         except IOError, ex:
   753         except IOError, ex:
   755             import errno
   754             import errno
   756             if ex.errno != errno.ENOENT:
   755             if ex.errno != errno.ENOENT:
   757                 raise
   756                 raise
   758             # bootstrap_cubes files doesn't exist
   757             # bootstrap_cubes files doesn't exist
   759             # set creating to notify this is not a regular start
   758             # notify this is not a regular start
   760             config.creating = True
   759             config.repairing = True
   761             # create an in-memory repository, will call config.init_cubes()
   760             # create an in-memory repository, will call config.init_cubes()
   762             config.repository()
   761             config.repository()
   763         except AttributeError:
   762         except AttributeError:
   764             # web only config
   763             # web only config
   765             config.init_cubes(config.repository().get_cubes())
   764             config.init_cubes(config.repository().get_cubes())
   789         """run the command with its specific arguments"""
   788         """run the command with its specific arguments"""
   790         for cube in cwcfg.available_cubes():
   789         for cube in cwcfg.available_cubes():
   791             print cube
   790             print cube
   792 
   791 
   793 register_commands((ListCommand,
   792 register_commands((ListCommand,
   794                    CreateApplicationCommand,
   793                    CreateInstanceCommand,
   795                    DeleteApplicationCommand,
   794                    DeleteInstanceCommand,
   796                    StartApplicationCommand,
   795                    StartInstanceCommand,
   797                    StopApplicationCommand,
   796                    StopInstanceCommand,
   798                    RestartApplicationCommand,
   797                    RestartInstanceCommand,
   799                    ReloadConfigurationCommand,
   798                    ReloadConfigurationCommand,
   800                    StatusCommand,
   799                    StatusCommand,
   801                    UpgradeApplicationCommand,
   800                    UpgradeInstanceCommand,
   802                    ShellCommand,
   801                    ShellCommand,
   803                    RecompileApplicationCatalogsCommand,
   802                    RecompileInstanceCatalogsCommand,
   804                    ListInstancesCommand, ListCubesCommand,
   803                    ListInstancesCommand, ListCubesCommand,
   805                    ))
   804                    ))
   806 
   805 
   807 
   806 
   808 def run(args):
   807 def run(args):