server/serverctl.py
changeset 6139 f76599a96238
parent 6138 65f5e488f983
child 6184 da580218a5b3
equal deleted inserted replaced
6102:27c47d239739 6139:f76599a96238
    24 # completion). So import locally in command helpers.
    24 # completion). So import locally in command helpers.
    25 import sys
    25 import sys
    26 import os
    26 import os
    27 
    27 
    28 from logilab.common.configuration import Configuration
    28 from logilab.common.configuration import Configuration
    29 from logilab.common.clcommands import register_commands, cmd_run, pop_arg
       
    30 from logilab.common.shellutils import ASK
    29 from logilab.common.shellutils import ASK
    31 
    30 
    32 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
    31 from cubicweb import AuthenticationError, ExecutionError, ConfigurationError
    33 from cubicweb.toolsutils import Command, CommandHandler, underline_title
    32 from cubicweb.toolsutils import Command, CommandHandler, underline_title
       
    33 from cubicweb.cwctl import CWCTL
    34 from cubicweb.server import SOURCE_TYPES
    34 from cubicweb.server import SOURCE_TYPES
    35 from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
    35 from cubicweb.server.serverconfig import (USER_OPTIONS, ServerConfiguration,
    36                                           SourceConfiguration)
    36                                           SourceConfiguration)
    37 
    37 
    38 # utility functions ###########################################################
    38 # utility functions ###########################################################
   215         config.write_bootstrap_cubes_file(cubes)
   215         config.write_bootstrap_cubes_file(cubes)
   216 
   216 
   217     def postcreate(self):
   217     def postcreate(self):
   218         if ASK.confirm('Run db-create to create the system database ?'):
   218         if ASK.confirm('Run db-create to create the system database ?'):
   219             verbosity = (self.config.mode == 'installed') and 'y' or 'n'
   219             verbosity = (self.config.mode == 'installed') and 'y' or 'n'
   220             cmd_run('db-create', self.config.appid, '--verbose=%s' % verbosity)
   220             CWCTL.run(['db-create', self.config.appid, '--verbose=%s' % verbosity])
   221         else:
   221         else:
   222             print ('-> nevermind, you can do it later with '
   222             print ('-> nevermind, you can do it later with '
   223                    '"cubicweb-ctl db-create %s".' % self.config.appid)
   223                    '"cubicweb-ctl db-create %s".' % self.config.appid)
   224 
   224 
   225 
   225 
   297     <instance>
   297     <instance>
   298       the identifier of the instance to initialize.
   298       the identifier of the instance to initialize.
   299     """
   299     """
   300     name = 'db-create'
   300     name = 'db-create'
   301     arguments = '<instance>'
   301     arguments = '<instance>'
   302 
   302     min_args = max_args = 1
   303     options = (
   303     options = (
   304         ('create-db',
   304         ('create-db',
   305          {'short': 'c', 'type': 'yn', 'metavar': '<y or n>',
   305          {'short': 'c', 'type': 'yn', 'metavar': '<y or n>',
   306           'default': True,
   306           'default': True,
   307           'help': 'create the database (yes by default)'}),
   307           'help': 'create the database (yes by default)'}),
   321     def run(self, args):
   321     def run(self, args):
   322         """run the command with its specific arguments"""
   322         """run the command with its specific arguments"""
   323         from logilab.database import get_db_helper
   323         from logilab.database import get_db_helper
   324         verbose = self.get('verbose')
   324         verbose = self.get('verbose')
   325         automatic = self.get('automatic')
   325         automatic = self.get('automatic')
   326         appid = pop_arg(args, msg='No instance specified !')
   326         appid = args.pop()
   327         config = ServerConfiguration.config_for(appid)
   327         config = ServerConfiguration.config_for(appid)
   328         source = config.sources()['system']
   328         source = config.sources()['system']
   329         dbname = source['db-name']
   329         dbname = source['db-name']
   330         driver = source['db-driver']
   330         driver = source['db-driver']
   331         helper = get_db_helper(driver)
   331         helper = get_db_helper(driver)
   369         cursor.close()
   369         cursor.close()
   370         cnx.commit()
   370         cnx.commit()
   371         print '-> database for instance %s created and necessary extensions installed.' % appid
   371         print '-> database for instance %s created and necessary extensions installed.' % appid
   372         print
   372         print
   373         if automatic or ASK.confirm('Run db-init to initialize the system database ?'):
   373         if automatic or ASK.confirm('Run db-init to initialize the system database ?'):
   374             cmd_run('db-init', config.appid)
   374             CWCTL.run(['db-init', config.appid])
   375         else:
   375         else:
   376             print ('-> nevermind, you can do it later with '
   376             print ('-> nevermind, you can do it later with '
   377                    '"cubicweb-ctl db-init %s".' % config.appid)
   377                    '"cubicweb-ctl db-init %s".' % config.appid)
   378 
   378 
   379 
   379 
   387     <instance>
   387     <instance>
   388       the identifier of the instance to initialize.
   388       the identifier of the instance to initialize.
   389     """
   389     """
   390     name = 'db-init'
   390     name = 'db-init'
   391     arguments = '<instance>'
   391     arguments = '<instance>'
   392 
   392     min_args = max_args = 1
   393     options = (
   393     options = (
   394         ('drop',
   394         ('drop',
   395          {'short': 'd', 'action': 'store_true',
   395          {'short': 'd', 'action': 'store_true',
   396           'default': False,
   396           'default': False,
   397           'help': 'insert drop statements to remove previously existant \
   397           'help': 'insert drop statements to remove previously existant \
   400 
   400 
   401     def run(self, args):
   401     def run(self, args):
   402         print '\n'+underline_title('Initializing the system database')
   402         print '\n'+underline_title('Initializing the system database')
   403         from cubicweb.server import init_repository
   403         from cubicweb.server import init_repository
   404         from logilab.database import get_connection
   404         from logilab.database import get_connection
   405         appid = pop_arg(args, msg='No instance specified !')
   405         appid = args[0]
   406         config = ServerConfiguration.config_for(appid)
   406         config = ServerConfiguration.config_for(appid)
   407         try:
   407         try:
   408             system = config.sources()['system']
   408             system = config.sources()['system']
   409             extra_args=system.get('db-extra-arguments')
   409             extra_args=system.get('db-extra-arguments')
   410             extra = extra_args and {'extra_args': extra_args} or {}
   410             extra = extra_args and {'extra_args': extra_args} or {}
   429     <user>
   429     <user>
   430       the database's user requiring grant access
   430       the database's user requiring grant access
   431     """
   431     """
   432     name = 'db-grant-user'
   432     name = 'db-grant-user'
   433     arguments = '<instance> <user>'
   433     arguments = '<instance> <user>'
   434 
   434     min_args = max_args = 2
   435     options = (
   435     options = (
   436         ('set-owner',
   436         ('set-owner',
   437          {'short': 'o', 'type' : 'yn', 'metavar' : '<yes or no>',
   437          {'short': 'o', 'type' : 'yn', 'metavar' : '<yes or no>',
   438           'default' : False,
   438           'default' : False,
   439           'help': 'Set the user as tables owner if yes (no by default).'}
   439           'help': 'Set the user as tables owner if yes (no by default).'}
   440          ),
   440          ),
   441         )
   441         )
   442     def run(self, args):
   442     def run(self, args):
   443         """run the command with its specific arguments"""
   443         """run the command with its specific arguments"""
   444         from cubicweb.server.sqlutils import sqlexec, sqlgrants
   444         from cubicweb.server.sqlutils import sqlexec, sqlgrants
   445         appid = pop_arg(args, 1, msg='No instance specified !')
   445         appid, user = args
   446         user = pop_arg(args, msg='No user specified !')
       
   447         config = ServerConfiguration.config_for(appid)
   446         config = ServerConfiguration.config_for(appid)
   448         source = config.sources()['system']
   447         source = config.sources()['system']
   449         set_owner = self.config.set_owner
   448         set_owner = self.config.set_owner
   450         cnx = system_source_cnx(source, special_privs='GRANT')
   449         cnx = system_source_cnx(source, special_privs='GRANT')
   451         cursor = cnx.cursor()
   450         cursor = cnx.cursor()
   455                               set_owner=set_owner), cursor)
   454                               set_owner=set_owner), cursor)
   456         except Exception, ex:
   455         except Exception, ex:
   457             cnx.rollback()
   456             cnx.rollback()
   458             import traceback
   457             import traceback
   459             traceback.print_exc()
   458             traceback.print_exc()
   460             print '-> an error occured:', ex
   459             print '-> an error occurred:', ex
   461         else:
   460         else:
   462             cnx.commit()
   461             cnx.commit()
   463             print '-> rights granted to %s on instance %s.' % (appid, user)
   462             print '-> rights granted to %s on instance %s.' % (appid, user)
   464 
   463 
   465 
   464 
   473     arguments = '<instance>'
   472     arguments = '<instance>'
   474 
   473 
   475     def run(self, args):
   474     def run(self, args):
   476         """run the command with its specific arguments"""
   475         """run the command with its specific arguments"""
   477         from cubicweb.server.utils import crypt_password, manager_userpasswd
   476         from cubicweb.server.utils import crypt_password, manager_userpasswd
   478         appid = pop_arg(args, 1, msg='No instance specified !')
   477         appid = args[0]
   479         config = ServerConfiguration.config_for(appid)
   478         config = ServerConfiguration.config_for(appid)
   480         sourcescfg = config.read_sources_file()
   479         sourcescfg = config.read_sources_file()
   481         try:
   480         try:
   482             adminlogin = sourcescfg['admin']['login']
   481             adminlogin = sourcescfg['admin']['login']
   483         except KeyError:
   482         except KeyError:
   507             config.write_sources_file(sourcescfg)
   506             config.write_sources_file(sourcescfg)
   508         except Exception, ex:
   507         except Exception, ex:
   509             cnx.rollback()
   508             cnx.rollback()
   510             import traceback
   509             import traceback
   511             traceback.print_exc()
   510             traceback.print_exc()
   512             print '-> an error occured:', ex
   511             print '-> an error occurred:', ex
   513         else:
   512         else:
   514             cnx.commit()
   513             cnx.commit()
   515             print '-> password reset, sources file regenerated.'
   514             print '-> password reset, sources file regenerated.'
   516         cnx.close()
   515         cnx.close()
   517 
   516 
   524     <instance>
   523     <instance>
   525       the identifier of the instance to initialize.
   524       the identifier of the instance to initialize.
   526     """
   525     """
   527     name = 'start-repository'
   526     name = 'start-repository'
   528     arguments = '<instance>'
   527     arguments = '<instance>'
   529 
   528     min_args = max_args = 1
   530     options = (
   529     options = (
   531         ('debug',
   530         ('debug',
   532          {'short': 'D', 'action' : 'store_true',
   531          {'short': 'D', 'action' : 'store_true',
   533           'help': 'start server in debug mode.'}),
   532           'help': 'start server in debug mode.'}),
   534         ('loglevel',
   533         ('loglevel',
   540 
   539 
   541     def run(self, args):
   540     def run(self, args):
   542         from logilab.common.daemon import daemonize
   541         from logilab.common.daemon import daemonize
   543         from cubicweb.cwctl import init_cmdline_log_threshold
   542         from cubicweb.cwctl import init_cmdline_log_threshold
   544         from cubicweb.server.server import RepositoryServer
   543         from cubicweb.server.server import RepositoryServer
   545         appid = pop_arg(args, msg='No instance specified !')
   544         appid = args[0]
   546         debug = self['debug']
   545         debug = self['debug']
   547         if sys.platform == 'win32' and not debug:
   546         if sys.platform == 'win32' and not debug:
   548             from logging import getLogger
   547             from logging import getLogger
   549             logger = getLogger('cubicweb.ctl')
   548             logger = getLogger('cubicweb.ctl')
   550             logger.info('Forcing debug mode on win32 platform')
   549             logger.info('Forcing debug mode on win32 platform')
   593     if os.system(cmd):
   592     if os.system(cmd):
   594         raise ExecutionError('Error while retrieving the dump at /tmp/%s' % filename)
   593         raise ExecutionError('Error while retrieving the dump at /tmp/%s' % filename)
   595     rmcmd = 'ssh -t %s "rm -f /tmp/%s"' % (host, filename)
   594     rmcmd = 'ssh -t %s "rm -f /tmp/%s"' % (host, filename)
   596     print rmcmd
   595     print rmcmd
   597     if os.system(rmcmd) and not ASK.confirm(
   596     if os.system(rmcmd) and not ASK.confirm(
   598         'An error occured while deleting remote dump at /tmp/%s. '
   597         'An error occurred while deleting remote dump at /tmp/%s. '
   599         'Continue anyway?' % filename):
   598         'Continue anyway?' % filename):
   600         raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename)
   599         raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename)
   601 
   600 
   602 def _local_dump(appid, output):
   601 def _local_dump(appid, output):
   603     config = ServerConfiguration.config_for(appid)
   602     config = ServerConfiguration.config_for(appid)
   671       the identifier of the instance to backup
   670       the identifier of the instance to backup
   672       format [[user@]host:]appname
   671       format [[user@]host:]appname
   673     """
   672     """
   674     name = 'db-dump'
   673     name = 'db-dump'
   675     arguments = '<instance>'
   674     arguments = '<instance>'
   676 
   675     min_args = max_args = 1
   677     options = (
   676     options = (
   678         ('output',
   677         ('output',
   679          {'short': 'o', 'type' : 'string', 'metavar' : '<file>',
   678          {'short': 'o', 'type' : 'string', 'metavar' : '<file>',
   680           'default' : None,
   679           'default' : None,
   681           'help': 'Specify the backup file where the backup will be stored.'}
   680           'help': 'Specify the backup file where the backup will be stored.'}
   686           'help': 'Use sudo on the remote host.'}
   685           'help': 'Use sudo on the remote host.'}
   687          ),
   686          ),
   688         )
   687         )
   689 
   688 
   690     def run(self, args):
   689     def run(self, args):
   691         appid = pop_arg(args, 1, msg='No instance specified !')
   690         appid = args[0]
   692         if ':' in appid:
   691         if ':' in appid:
   693             host, appid = appid.split(':')
   692             host, appid = appid.split(':')
   694             _remote_dump(host, appid, self.config.output, self.config.sudo)
   693             _remote_dump(host, appid, self.config.output, self.config.sudo)
   695         else:
   694         else:
   696             _local_dump(appid, self.config.output)
   695             _local_dump(appid, self.config.output)
   702     <instance>
   701     <instance>
   703       the identifier of the instance to restore
   702       the identifier of the instance to restore
   704     """
   703     """
   705     name = 'db-restore'
   704     name = 'db-restore'
   706     arguments = '<instance> <backupfile>'
   705     arguments = '<instance> <backupfile>'
       
   706     min_args = max_args = 2
   707 
   707 
   708     options = (
   708     options = (
   709         ('no-drop',
   709         ('no-drop',
   710          {'short': 'n', 'action' : 'store_true', 'default' : False,
   710          {'short': 'n', 'action' : 'store_true', 'default' : False,
   711           'help': 'for some reason the database doesn\'t exist and so '
   711           'help': 'for some reason the database doesn\'t exist and so '
   719           'timestamp of the backup to restore, not a file'}
   719           'timestamp of the backup to restore, not a file'}
   720          ),
   720          ),
   721         )
   721         )
   722 
   722 
   723     def run(self, args):
   723     def run(self, args):
   724         appid = pop_arg(args, 1, msg='No instance specified !')
   724         appid, backupfile = args
   725         backupfile = pop_arg(args, msg='No backup file or timestamp specified !')
       
   726         _local_restore(appid, backupfile,
   725         _local_restore(appid, backupfile,
   727                        drop=not self.config.no_drop,
   726                        drop=not self.config.no_drop,
   728                        systemonly=not self.config.restore_all)
   727                        systemonly=not self.config.restore_all)
   729 
   728 
   730 
   729 
   738     <dest-instance>
   737     <dest-instance>
   739       the identifier of the instance to restore
   738       the identifier of the instance to restore
   740     """
   739     """
   741     name = 'db-copy'
   740     name = 'db-copy'
   742     arguments = '<src-instance> <dest-instance>'
   741     arguments = '<src-instance> <dest-instance>'
   743 
   742     min_args = max_args = 2
   744     options = (
   743     options = (
   745         ('no-drop',
   744         ('no-drop',
   746          {'short': 'n', 'action' : 'store_true',
   745          {'short': 'n', 'action' : 'store_true',
   747           'default' : False,
   746           'default' : False,
   748           'help': 'For some reason the database doesn\'t exist and so '
   747           'help': 'For some reason the database doesn\'t exist and so '
   760          ),
   759          ),
   761         )
   760         )
   762 
   761 
   763     def run(self, args):
   762     def run(self, args):
   764         import tempfile
   763         import tempfile
   765         srcappid = pop_arg(args, 1, msg='No source instance specified !')
   764         srcappid, destappid = args
   766         destappid = pop_arg(args, msg='No destination instance specified !')
       
   767         fd, output = tempfile.mkstemp()
   765         fd, output = tempfile.mkstemp()
   768         os.close(fd)
   766         os.close(fd)
   769         if ':' in srcappid:
   767         if ':' in srcappid:
   770             host, srcappid = srcappid.split(':')
   768             host, srcappid = srcappid.split(':')
   771             _remote_dump(host, srcappid, output, self.config.sudo)
   769             _remote_dump(host, srcappid, output, self.config.sudo)
   784     <instance>
   782     <instance>
   785       the identifier of the instance to check
   783       the identifier of the instance to check
   786     """
   784     """
   787     name = 'db-check'
   785     name = 'db-check'
   788     arguments = '<instance>'
   786     arguments = '<instance>'
   789 
   787     min_args = max_args = 1
   790     options = (
   788     options = (
   791         ('checks',
   789         ('checks',
   792          {'short': 'c', 'type' : 'csv', 'metavar' : '<check list>',
   790          {'short': 'c', 'type' : 'csv', 'metavar' : '<check list>',
   793           'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
   791           'default' : ('entities', 'relations', 'metadata', 'schema', 'text_index'),
   794           'help': 'Comma separated list of check to run. By default run all \
   792           'help': 'Comma separated list of check to run. By default run all \
   814          ),
   812          ),
   815 
   813 
   816         )
   814         )
   817 
   815 
   818     def run(self, args):
   816     def run(self, args):
   819         from cubicweb.server.checkintegrity import check
   817         appid = args[0]
   820         appid = pop_arg(args, 1, msg='No instance specified !')
       
   821         config = ServerConfiguration.config_for(appid)
   818         config = ServerConfiguration.config_for(appid)
   822         config.repairing = self.config.force
   819         config.repairing = self.config.force
   823         repo, cnx = repo_cnx(config)
   820         repo, cnx = repo_cnx(config)
   824         check(repo, cnx,
   821         check(repo, cnx,
   825               self.config.checks, self.config.reindex, self.config.autofix)
   822               self.config.checks, self.config.reindex, self.config.autofix)
   831     <instance>
   828     <instance>
   832       the identifier of the instance to rebuild
   829       the identifier of the instance to rebuild
   833     """
   830     """
   834     name = 'db-rebuild-fti'
   831     name = 'db-rebuild-fti'
   835     arguments = '<instance>'
   832     arguments = '<instance>'
   836 
   833     min_args = max_args = 1
   837     options = ()
       
   838 
   834 
   839     def run(self, args):
   835     def run(self, args):
   840         from cubicweb.server.checkintegrity import reindex_entities
   836         from cubicweb.server.checkintegrity import reindex_entities
   841         appid = pop_arg(args, 1, msg='No instance specified !')
   837         appid = args[0]
   842         config = ServerConfiguration.config_for(appid)
   838         config = ServerConfiguration.config_for(appid)
   843         repo, cnx = repo_cnx(config)
   839         repo, cnx = repo_cnx(config)
   844         session = repo._get_session(cnx.sessionid, setpool=True)
   840         session = repo._get_session(cnx.sessionid, setpool=True)
   845         reindex_entities(repo.schema, session)
   841         reindex_entities(repo.schema, session)
   846         cnx.commit()
   842         cnx.commit()
   855     <instance>
   851     <instance>
   856       the identifier of the instance to synchronize.
   852       the identifier of the instance to synchronize.
   857     """
   853     """
   858     name = 'schema-sync'
   854     name = 'schema-sync'
   859     arguments = '<instance>'
   855     arguments = '<instance>'
   860 
   856     min_args = max_args = 1
   861     def run(self, args):
   857 
   862         appid = pop_arg(args, msg='No instance specified !')
   858     def run(self, args):
       
   859         appid = args[0]
   863         config = ServerConfiguration.config_for(appid)
   860         config = ServerConfiguration.config_for(appid)
   864         mih = config.migration_handler()
   861         mih = config.migration_handler()
   865         mih.cmd_synchronize_schema()
   862         mih.cmd_synchronize_schema()
   866 
   863 
   867 
   864 
   868 register_commands( (CreateInstanceDBCommand,
   865 class CheckMappingCommand(Command):
   869                     InitInstanceCommand,
   866     """Check content of the mapping file of an external source.
   870                     GrantUserOnInstanceCommand,
   867 
   871                     ResetAdminPasswordCommand,
   868     The mapping is checked against the instance's schema, searching for
   872                     StartRepositoryCommand,
   869     inconsistencies or stuff you may have forgotten. It's higly recommanded to
   873                     DBDumpCommand,
   870     run it when you setup a multi-sources instance.
   874                     DBRestoreCommand,
   871 
   875                     DBCopyCommand,
   872     <instance>
   876                     CheckRepositoryCommand,
   873       the identifier of the instance.
   877                     RebuildFTICommand,
   874 
   878                     SynchronizeInstanceSchemaCommand,
   875     <mapping file>
   879                     ) )
   876       the mapping file to check.
       
   877     """
       
   878     name = 'check-mapping'
       
   879     arguments = '<instance> <mapping file>'
       
   880     min_args = max_args = 2
       
   881 
       
   882     def run(self, args):
       
   883         from cubicweb.server.checkintegrity import check_mapping
       
   884         from cubicweb.server.sources.pyrorql import load_mapping_file
       
   885         appid, mappingfile = args
       
   886         config = ServerConfiguration.config_for(appid)
       
   887         config.quick_start = True
       
   888         mih = config.migration_handler(connect=False, verbosity=1)
       
   889         repo = mih.repo_connect() # necessary to get cubes
       
   890         check_mapping(config.load_schema(), load_mapping_file(mappingfile))
       
   891 
       
   892 for cmdclass in (CreateInstanceDBCommand, InitInstanceCommand,
       
   893                  GrantUserOnInstanceCommand, ResetAdminPasswordCommand,
       
   894                  StartRepositoryCommand,
       
   895                  DBDumpCommand, DBRestoreCommand, DBCopyCommand,
       
   896                  CheckRepositoryCommand, RebuildFTICommand,
       
   897                  SynchronizeInstanceSchemaCommand,
       
   898                  CheckMappingCommand,
       
   899                  ):
       
   900     CWCTL.register(cmdclass)