merge with 3.21.3
The change in unittest_serverctl.py is needed because of daef7ce08fea
(from 3.20.11) and 3914388b2d0f (from the 3.22 branch). Due to both
changes, CubicWebTC.config.repository no longer creates a new repository
(and thus, a new connection). Since both DBDumpCommand and
CubicWebTC.tearDown try to shutdown the repo, tearDown breaks apart.
The solution is to temporarily disable ServerConfiguration's config
cache. By forcing DBDumpCommand to get a new configuration object, it
then gets its own Repo object, allowing tearDown and DBDumpCommand to
work independently.
# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""cubicweb-ctl commands and command handlers specific to the repository"""from__future__importprint_function__docformat__='restructuredtext en'# *ctl module should limit the number of import to be imported as quickly as# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash# completion). So import locally in command helpers.importsysimportosfromcontextlibimportcontextmanagerimportloggingimportsubprocessfromsiximportstring_typesfromsix.movesimportinputfromlogilab.commonimportnullobjectfromlogilab.common.configurationimportConfiguration,merge_optionsfromlogilab.common.shellutilsimportASK,generate_passwordfromlogilab.databaseimportget_db_helper,get_connectionfromcubicwebimportAuthenticationError,ExecutionError,ConfigurationErrorfromcubicweb.toolsutilsimportCommand,CommandHandler,underline_titlefromcubicweb.cwctlimportCWCTL,check_options_consistency,ConfigureInstanceCommandfromcubicweb.serverimportSOURCE_TYPESfromcubicweb.server.serverconfigimport(USER_OPTIONS,ServerConfiguration,SourceConfiguration,ask_source_config,generate_source_config)# utility functions ###########################################################defsource_cnx(source,dbname=None,special_privs=False,interactive=True):"""open and return a connection to the system database defined in the given server.serverconfig """fromgetpassimportgetpassdbhost=source.get('db-host')ifdbnameisNone:dbname=source['db-name']driver=source['db-driver']dbhelper=get_db_helper(driver)ifinteractive:print('-> connecting to %s database'%driver,end=' ')ifdbhost:print('%s@%s'%(dbname,dbhost),end=' ')else:print(dbname,end=' ')ifdbhelper.users_support:ifnotinteractiveor(notspecial_privsandsource.get('db-user')):user=source.get('db-user',os.environ.get('USER',''))ifinteractive:print('as',user)password=source.get('db-password')else:print()ifspecial_privs:print('WARNING')print('the user will need the following special access rights ''on the database:')print(special_privs)print()default_user=source.get('db-user',os.environ.get('USER',''))user=input('Connect as user ? [%r]: '%default_user)user=user.strip()ordefault_userifuser==source.get('db-user'):password=source.get('db-password')else:password=getpass('password: ')else:user=password=Noneextra_args=source.get('db-extra-arguments')extra=extra_argsand{'extra_args':extra_args}or{}cnx=get_connection(driver,dbhost,dbname,user,password=password,port=source.get('db-port'),schema=source.get('db-namespace'),**extra)try:cnx.logged_user=userexceptAttributeError:# C object, __slots__fromlogilab.databaseimport_SimpleConnectionWrappercnx=_SimpleConnectionWrapper(cnx)cnx.logged_user=userreturncnxdefsystem_source_cnx(source,dbms_system_base=False,special_privs='CREATE/DROP DATABASE',interactive=True):"""shortcut to get a connextion to the instance system database defined in the given config. If <dbms_system_base> is True, connect to the dbms system database instead (for task such as create/drop the instance database) """ifdbms_system_base:system_db=get_db_helper(source['db-driver']).system_database()returnsource_cnx(source,system_db,special_privs=special_privs,interactive=interactive)returnsource_cnx(source,special_privs=special_privs,interactive=interactive)def_db_sys_cnx(source,special_privs,interactive=True):"""return a connection on the RDMS system table (to create/drop a user or a database) """importlogilab.commonaslgplgp.USE_MX_DATETIME=Falsedriver=source['db-driver']helper=get_db_helper(driver)# connect on the dbms system base to create our basecnx=system_source_cnx(source,True,special_privs=special_privs,interactive=interactive)# disable autocommit (isolation_level(1)) because DROP and# CREATE DATABASE can't be executed in a transactionset_isolation_level=getattr(cnx,'set_isolation_level',None)ifset_isolation_levelisnotNone:# set_isolation_level() is psycopg specificset_isolation_level(0)returncnxdefrepo_cnx(config):"""return a in-memory repository and a repoapi connection to it"""fromcubicwebimportrepoapifromcubicweb.server.utilsimportmanager_userpasswdtry:login=config.default_admin_config['login']pwd=config.default_admin_config['password']exceptKeyError:login,pwd=manager_userpasswd()whileTrue:try:repo=repoapi.get_repository(config=config)cnx=repoapi.connect(repo,login,password=pwd)returnrepo,cnxexceptAuthenticationError:print('-> Error: wrong user/password.')# reset cubes else we'll have an assertion error on next retryconfig._cubes=Nonelogin,pwd=manager_userpasswd()# repository specific command handlers ########################################classRepositoryCreateHandler(CommandHandler):cmdname='create'cfgname='repository'defbootstrap(self,cubes,automatic=False,inputlevel=0):"""create an instance by copying files from the given cube and by asking information necessary to build required configuration files """config=self.configifnotautomatic:print(underline_title('Configuring the repository'))config.input_config('email',inputlevel)print('\n'+underline_title('Configuring the sources'))sourcesfile=config.sources_file()# hack to make Method('default_instance_id') usable in db option defs# (in native.py)sconfig=SourceConfiguration(config,options=SOURCE_TYPES['native'].options)ifnotautomatic:sconfig.input_config(inputlevel=inputlevel)print()sourcescfg={'system':sconfig}ifautomatic:# XXX modify a copypassword=generate_password()print('-> set administrator account to admin / %s'%password)USER_OPTIONS[1][1]['default']=passwordsconfig=Configuration(options=USER_OPTIONS)else:sconfig=Configuration(options=USER_OPTIONS)sconfig.input_config(inputlevel=inputlevel)sourcescfg['admin']=sconfigconfig.write_sources_file(sourcescfg)# remember selected cubes for later initialization of the databaseconfig.write_bootstrap_cubes_file(cubes)defpostcreate(self,automatic=False,inputlevel=0):ifautomatic:CWCTL.run(['db-create','--automatic',self.config.appid])elifASK.confirm('Run db-create to create the system database ?'):CWCTL.run(['db-create','--config-level',str(inputlevel),self.config.appid])else:print('-> nevermind, you can do it later with ''"cubicweb-ctl db-create %s".'%self.config.appid)@contextmanagerdefdb_transaction(source,privilege):"""Open a transaction to the instance database"""cnx=system_source_cnx(source,special_privs=privilege)cursor=cnx.cursor()try:yieldcursorexcept:cnx.rollback()cnx.close()raiseelse:cnx.commit()cnx.close()@contextmanagerdefdb_sys_transaction(source,privilege):"""Open a transaction to the system database"""cnx=_db_sys_cnx(source,privilege)cursor=cnx.cursor()try:yieldcursorexcept:cnx.rollback()cnx.close()raiseelse:cnx.commit()cnx.close()classRepositoryDeleteHandler(CommandHandler):cmdname='delete'cfgname='repository'def_drop_namespace(self,source):db_namespace=source.get('db-namespace')withdb_transaction(source,privilege='DROP SCHEMA')ascursor:helper=get_db_helper(source['db-driver'])helper.drop_schema(cursor,db_namespace)print('-> database schema %s dropped'%db_namespace)def_drop_database(self,source):dbname=source['db-name']ifsource['db-driver']=='sqlite':print('deleting database file %(db-name)s'%source)os.unlink(source['db-name'])print('-> database %(db-name)s dropped.'%source)else:helper=get_db_helper(source['db-driver'])withdb_sys_transaction(source,privilege='DROP DATABASE')ascursor:print('dropping database %(db-name)s'%source)cursor.execute('DROP DATABASE "%(db-name)s"'%source)print('-> database %(db-name)s dropped.'%source)def_drop_user(self,source):user=source['db-user']orNoneifuserisnotNone:withdb_sys_transaction(source,privilege='DROP USER')ascursor:print('dropping user %s'%user)cursor.execute('DROP USER %s'%user)def_cleanup_steps(self,source):# 1/ delete namespace if useddb_namespace=source.get('db-namespace')ifdb_namespace:yield('Delete database namespace "%s"'%db_namespace,self._drop_namespace,True)# 2/ delete databaseyield('Delete database "%(db-name)s"'%source,self._drop_database,True)# 3/ delete userhelper=get_db_helper(source['db-driver'])ifsource['db-user']andhelper.users_support:# XXX should check we are not connected as useryield('Delete user "%(db-user)s"'%source,self._drop_user,False)defcleanup(self):"""remove instance's configuration and database"""source=self.config.system_source_configformsg,step,defaultinself._cleanup_steps(source):ifASK.confirm(msg,default_is_yes=default):try:step(source)exceptExceptionasexc:print('ERROR',exc)ifASK.confirm('An error occurred. Continue anyway?',default_is_yes=False):continueraiseExecutionError(str(exc))# repository specific commands ################################################defcreatedb(helper,source,dbcnx,cursor,**kwargs):ifdbcnx.logged_user!=source['db-user']:helper.create_database(cursor,source['db-name'],source['db-user'],source['db-encoding'],**kwargs)else:helper.create_database(cursor,source['db-name'],dbencoding=source['db-encoding'],**kwargs)classCreateInstanceDBCommand(Command):"""Create the system database of an instance (run after 'create'). You will be prompted for a login / password to use to connect to the system database. The given user should have almost all rights on the database (ie a super user on the DBMS allowed to create database, users, languages...). <instance> the identifier of the instance to initialize. """name='db-create'arguments='<instance>'min_args=max_args=1options=(('automatic',{'short':'a','action':'store_true','default':False,'help':'automatic mode: never ask and use default answer to every ''question. this may require that your login match a database super ''user (allowed to create database & all).',}),('config-level',{'short':'l','type':'int','metavar':'<level>','default':0,'help':'configuration level (0..2): 0 will ask for essential ''configuration parameters only while 2 will ask for all parameters',}),('create-db',{'short':'c','type':'yn','metavar':'<y or n>','default':True,'help':'create the database (yes by default)'}),)defrun(self,args):"""run the command with its specific arguments"""check_options_consistency(self.config)automatic=self.get('automatic')appid=args.pop()config=ServerConfiguration.config_for(appid)source=config.system_source_configdbname=source['db-name']driver=source['db-driver']helper=get_db_helper(driver)ifdriver=='sqlite':ifos.path.exists(dbname)and(automaticorASK.confirm('Database %s already exists. Drop it?'%dbname)):os.unlink(dbname)elifself.config.create_db:print('\n'+underline_title('Creating the system database'))# connect on the dbms system base to create our basedbcnx=_db_sys_cnx(source,'CREATE/DROP DATABASE and / or USER',interactive=notautomatic)cursor=dbcnx.cursor()try:ifhelper.users_support:user=source['db-user']ifnothelper.user_exists(cursor,user)and(automaticor \ASK.confirm('Create db user %s ?'%user,default_is_yes=False)):helper.create_user(source['db-user'],source.get('db-password'))print('-> user %s created.'%user)ifdbnameinhelper.list_databases(cursor):ifautomaticorASK.confirm('Database %s already exists -- do you want to drop it ?'%dbname):cursor.execute('DROP DATABASE "%s"'%dbname)else:print('you may want to run "cubicweb-ctl db-init ''--drop %s" manually to continue.'%config.appid)returncreatedb(helper,source,dbcnx,cursor)dbcnx.commit()print('-> database %s created.'%dbname)exceptBaseException:dbcnx.rollback()raisecnx=system_source_cnx(source,special_privs='CREATE LANGUAGE/SCHEMA',interactive=notautomatic)cursor=cnx.cursor()helper.init_fti_extensions(cursor)namespace=source.get('db-namespace')ifnamespaceandASK.confirm('Create schema %s in database %s ?'%(namespace,dbname)):helper.create_schema(cursor,namespace)cnx.commit()# postgres specific stuffifdriver=='postgres':# install plpythonu/plpgsql languageslangs=('plpythonu','plpgsql')forextlanginlangs:ifautomaticorASK.confirm('Create language %s ?'%extlang):try:helper.create_language(cursor,extlang)exceptExceptionasexc:print('-> ERROR:',exc)print('-> could not create language %s, some stored procedures might be unusable'%extlang)cnx.rollback()else:cnx.commit()print('-> database for instance %s created and necessary extensions installed.'%appid)print()ifautomatic:CWCTL.run(['db-init','--automatic','--config-level','0',config.appid])elifASK.confirm('Run db-init to initialize the system database ?'):CWCTL.run(['db-init','--config-level',str(self.config.config_level),config.appid])else:print('-> nevermind, you can do it later with ''"cubicweb-ctl db-init %s".'%config.appid)classInitInstanceCommand(Command):"""Initialize the system database of an instance (run after 'db-create'). Notice this will be done using user specified in the sources files, so this user should have the create tables grant permissions on the database. <instance> the identifier of the instance to initialize. """name='db-init'arguments='<instance>'min_args=max_args=1options=(('automatic',{'short':'a','action':'store_true','default':False,'help':'automatic mode: never ask and use default answer to every ''question.',}),('config-level',{'short':'l','type':'int','default':0,'help':'level threshold for questions asked when configuring ''another source'}),('drop',{'short':'d','action':'store_true','default':False,'help':'insert drop statements to remove previously existant ''tables, indexes... (no by default)'}),)defrun(self,args):check_options_consistency(self.config)print('\n'+underline_title('Initializing the system database'))fromcubicweb.serverimportinit_repositoryappid=args[0]config=ServerConfiguration.config_for(appid)try:system=config.system_source_configextra_args=system.get('db-extra-arguments')extra=extra_argsand{'extra_args':extra_args}or{}get_connection(system['db-driver'],database=system['db-name'],host=system.get('db-host'),port=system.get('db-port'),user=system.get('db-user')or'',password=system.get('db-password')or'',schema=system.get('db-namespace'),**extra)exceptExceptionasex:raiseConfigurationError('You seem to have provided wrong connection information in '\'the %s file. Resolve this first (error: %s).'%(config.sources_file(),str(ex).strip()))init_repository(config,drop=self.config.drop)ifnotself.config.automatic:whileASK.confirm('Enter another source ?',default_is_yes=False):CWCTL.run(['source-add','--config-level',str(self.config.config_level),config.appid])classAddSourceCommand(Command):"""Add a data source to an instance. <instance> the identifier of the instance to initialize. """name='source-add'arguments='<instance>'min_args=max_args=1options=(('config-level',{'short':'l','type':'int','default':1,'help':'level threshold for questions asked when configuring another source'}),)defrun(self,args):appid=args[0]config=ServerConfiguration.config_for(appid)repo,cnx=repo_cnx(config)repo.hm.call_hooks('server_maintenance',repo=repo)try:withcnx:used=set(nforn,incnx.execute('Any SN WHERE S is CWSource, S name SN'))cubes=repo.get_cubes()whileTrue:type=input('source type (%s): '%', '.join(sorted(SOURCE_TYPES)))iftypenotinSOURCE_TYPES:print('-> unknown source type, use one of the available types.')continuesourcemodule=SOURCE_TYPES[type].moduleifnotsourcemodule.startswith('cubicweb.'):# module names look like cubes.mycube.themodulesourcecube=SOURCE_TYPES[type].module.split('.',2)[1]# if the source adapter is coming from an external component,# ensure it's specified in used cubesifnotsourcecubeincubes:print('-> this source type require the %s cube which is ''not used by the instance.')continuebreakwhileTrue:parser=input('parser type (%s): '%', '.join(sorted(repo.vreg['parsers'])))ifparserinrepo.vreg['parsers']:breakprint('-> unknown parser identifier, use one of the available types.')whileTrue:sourceuri=input('source identifier (a unique name used to ''tell sources apart): ').strip()ifnotsourceuri:print('-> mandatory.')else:sourceuri=unicode(sourceuri,sys.stdin.encoding)ifsourceuriinused:print('-> uri already used, choose another one.')else:breakurl=input('source URL (leave empty for none): ').strip()url=unicode(url)ifurlelseNone# XXX configurable inputlevelsconfig=ask_source_config(config,type,inputlevel=self.config.config_level)cfgstr=unicode(generate_source_config(sconfig),sys.stdin.encoding)cnx.create_entity('CWSource',name=sourceuri,type=unicode(type),config=cfgstr,parser=unicode(parser),url=unicode(url))cnx.commit()finally:repo.hm.call_hooks('server_shutdown')classGrantUserOnInstanceCommand(Command):"""Grant a database user on a repository system database. <instance> the identifier of the instance <user> the database's user requiring grant access """name='db-grant-user'arguments='<instance> <user>'min_args=max_args=2options=(('set-owner',{'short':'o','type':'yn','metavar':'<yes or no>','default':False,'help':'Set the user as tables owner if yes (no by default).'}),)defrun(self,args):"""run the command with its specific arguments"""fromcubicweb.server.sqlutilsimportsqlexec,sqlgrantsappid,user=argsconfig=ServerConfiguration.config_for(appid)source=config.system_source_configset_owner=self.config.set_ownercnx=system_source_cnx(source,special_privs='GRANT')cursor=cnx.cursor()schema=config.load_schema()try:sqlexec(sqlgrants(schema,source['db-driver'],user,set_owner=set_owner),cursor)exceptExceptionasex:cnx.rollback()importtracebacktraceback.print_exc()print('-> an error occurred:',ex)else:cnx.commit()print('-> rights granted to %s on instance %s.'%(appid,user))classResetAdminPasswordCommand(Command):"""Reset the administrator password. <instance> the identifier of the instance """name='reset-admin-pwd'arguments='<instance>'min_args=max_args=1options=(('password',{'short':'p','type':'string','metavar':'<new-password>','default':None,'help':'Use this password instead of prompt for one.\n''/!\ THIS IS AN INSECURE PRACTICE /!\ \n''the password will appear in shell history'}),)defrun(self,args):"""run the command with its specific arguments"""fromcubicweb.server.utilsimportcrypt_password,manager_userpasswdappid=args[0]config=ServerConfiguration.config_for(appid)sourcescfg=config.read_sources_file()try:adminlogin=sourcescfg['admin']['login']exceptKeyError:print('-> Error: could not get cubicweb administrator login.')sys.exit(1)cnx=source_cnx(sourcescfg['system'])driver=sourcescfg['system']['db-driver']dbhelper=get_db_helper(driver)cursor=cnx.cursor()# check admin existscursor.execute("SELECT * FROM cw_CWUser WHERE cw_login=%(l)s",{'l':adminlogin})ifnotcursor.fetchall():print("-> error: admin user %r specified in sources doesn't exist ""in the database"%adminlogin)print(" fix your sources file before running this command")cnx.close()sys.exit(1)ifself.config.passwordisNone:# ask for a new passwordmsg='new password for %s'%adminlogin_,pwd=manager_userpasswd(adminlogin,confirm=True,passwdmsg=msg)else:pwd=self.config.passwordtry:cursor.execute("UPDATE cw_CWUser SET cw_upassword=%(p)s WHERE cw_login=%(l)s",{'p':dbhelper.binary_value(crypt_password(pwd)),'l':adminlogin})sconfig=Configuration(options=USER_OPTIONS)sconfig['login']=adminloginsconfig['password']=pwdsourcescfg['admin']=sconfigconfig.write_sources_file(sourcescfg)exceptExceptionasex:cnx.rollback()importtracebacktraceback.print_exc()print('-> an error occurred:',ex)else:cnx.commit()print('-> password reset, sources file regenerated.')cnx.close()def_remote_dump(host,appid,output,sudo=False):# XXX generate unique/portable file namefromdatetimeimportdatefilename='%s-%s.tgz'%(appid,date.today().strftime('%Y-%m-%d'))dmpcmd='cubicweb-ctl db-dump -o /tmp/%s%s'%(filename,appid)ifsudo:dmpcmd='sudo %s'%(dmpcmd)dmpcmd='ssh -t %s "%s"'%(host,dmpcmd)print(dmpcmd)ifos.system(dmpcmd):raiseExecutionError('Error while dumping the database')ifoutputisNone:output=filenamecmd='scp %s:/tmp/%s%s'%(host,filename,output)print(cmd)ifos.system(cmd):raiseExecutionError('Error while retrieving the dump at /tmp/%s'%filename)rmcmd='ssh -t %s "rm -f /tmp/%s"'%(host,filename)print(rmcmd)ifos.system(rmcmd)andnotASK.confirm('An error occurred while deleting remote dump at /tmp/%s. ''Continue anyway?'%filename):raiseExecutionError('Error while deleting remote dump at /tmp/%s'%filename)def_local_dump(appid,output,format='native'):config=ServerConfiguration.config_for(appid)config.quick_start=Truemih=config.migration_handler(verbosity=1)mih.backup_database(output,askconfirm=False,format=format)mih.shutdown()def_local_restore(appid,backupfile,drop,format='native'):config=ServerConfiguration.config_for(appid)config.verbosity=1# else we won't be asked for confirmation on problemsconfig.quick_start=Truemih=config.migration_handler(connect=False,verbosity=1)mih.restore_database(backupfile,drop,askconfirm=False,format=format)repo=mih.repo# version of the databasedbversions=repo.get_versions()mih.shutdown()ifnotdbversions:print("bad or missing version information in the database, don't upgrade file system")return# version of installed softwareeversion=dbversions['cubicweb']status=instance_status(config,eversion,dbversions)# * database version > installed softwareifstatus=='needsoftupgrade':print("** The database of %s is more recent than the installed software!"%config.appid)print("** Upgrade your software, then migrate the database by running the command")print("** 'cubicweb-ctl upgrade %s'"%config.appid)return# * database version < installed software, an upgrade will be necessary# anyway, just rewrite vc.conf and warn user he has to upgradeelifstatus=='needapplupgrade':print("** The database of %s is older than the installed software."%config.appid)print("** Migrate the database by running the command")print("** 'cubicweb-ctl upgrade %s'"%config.appid)return# * database version = installed software, database version = instance fs version# ok!definstance_status(config,cubicwebapplversion,vcconf):cubicwebversion=config.cubicweb_version()ifcubicwebapplversion>cubicwebversion:return'needsoftupgrade'ifcubicwebapplversion<cubicwebversion:return'needapplupgrade'forcubeinconfig.cubes():try:softversion=config.cube_version(cube)exceptConfigurationError:print('-> Error: no cube version information for %s, please check that the cube is installed.'%cube)continuetry:applversion=vcconf[cube]exceptKeyError:print('-> Error: no cube version information for %s in version configuration.'%cube)continueifsoftversion==applversion:continueifsoftversion>applversion:return'needsoftupgrade'elifsoftversion<applversion:return'needapplupgrade'returnNoneclassDBDumpCommand(Command):"""Backup the system database of an instance. <instance> the identifier of the instance to backup format [[user@]host:]appname """name='db-dump'arguments='<instance>'min_args=max_args=1options=(('output',{'short':'o','type':'string','metavar':'<file>','default':None,'help':'Specify the backup file where the backup will be stored.'}),('sudo',{'short':'s','action':'store_true','default':False,'help':'Use sudo on the remote host.'}),('format',{'short':'f','default':'native','type':'choice','choices':('native','portable'),'help':'"native" format uses db backend utilities to dump the database. ''"portable" format uses a database independent format'}),)defrun(self,args):appid=args[0]if':'inappid:host,appid=appid.split(':')_remote_dump(host,appid,self.config.output,self.config.sudo)else:_local_dump(appid,self.config.output,format=self.config.format)classDBRestoreCommand(Command):"""Restore the system database of an instance. <instance> the identifier of the instance to restore """name='db-restore'arguments='<instance> <backupfile>'min_args=max_args=2options=(('no-drop',{'short':'n','action':'store_true','default':False,'help':'for some reason the database doesn\'t exist and so ''should not be dropped.'}),('format',{'short':'f','default':'native','type':'choice','choices':('native','portable'),'help':'the format used when dumping the database'}),)defrun(self,args):appid,backupfile=argsifself.config.format=='portable':# we need to ensure a DB exist before restoring from portable formatifnotself.config.no_drop:try:CWCTL.run(['db-create','--automatic',appid])exceptSystemExitasexc:# continue if the command exited with status 0 (success)ifexc.code:raise_local_restore(appid,backupfile,drop=notself.config.no_drop,format=self.config.format)ifself.config.format=='portable':try:CWCTL.run(['db-rebuild-fti',appid])exceptSystemExitasexc:ifexc.code:raiseclassDBCopyCommand(Command):"""Copy the system database of an instance (backup and restore). <src-instance> the identifier of the instance to backup format [[user@]host:]appname <dest-instance> the identifier of the instance to restore """name='db-copy'arguments='<src-instance> <dest-instance>'min_args=max_args=2options=(('no-drop',{'short':'n','action':'store_true','default':False,'help':'For some reason the database doesn\'t exist and so ''should not be dropped.'}),('keep-dump',{'short':'k','action':'store_true','default':False,'help':'Specify that the dump file should not be automatically removed.'}),('sudo',{'short':'s','action':'store_true','default':False,'help':'Use sudo on the remote host.'}),('format',{'short':'f','default':'native','type':'choice','choices':('native','portable'),'help':'"native" format uses db backend utilities to dump the database. ''"portable" format uses a database independent format'}),)defrun(self,args):importtempfilesrcappid,destappid=argsfd,output=tempfile.mkstemp()os.close(fd)if':'insrcappid:host,srcappid=srcappid.split(':')_remote_dump(host,srcappid,output,self.config.sudo)else:_local_dump(srcappid,output,format=self.config.format)_local_restore(destappid,output,notself.config.no_drop,self.config.format)ifself.config.keep_dump:print('-> you can get the dump file at',output)else:os.remove(output)classCheckRepositoryCommand(Command):"""Check integrity of the system database of an instance. <instance> the identifier of the instance to check """name='db-check'arguments='<instance>'min_args=max_args=1options=(('checks',{'short':'c','type':'csv','metavar':'<check list>','default':('entities','relations','mandatory_relations','mandatory_attributes','metadata','schema','text_index'),'help':'Comma separated list of check to run. By default run all \checks, i.e. entities, relations, mandatory_relations, mandatory_attributes, \metadata, text_index and schema.'}),('autofix',{'short':'a','type':'yn','metavar':'<yes or no>','default':False,'help':'Automatically correct integrity problems if this option \is set to "y" or "yes", else only display them'}),('reindex',{'short':'r','type':'yn','metavar':'<yes or no>','default':False,'help':'re-indexes the database for full text search if this \option is set to "y" or "yes" (may be long for large database).'}),('force',{'short':'f','action':'store_true','default':False,'help':'don\'t check instance is up to date.'}),)defrun(self,args):fromcubicweb.server.checkintegrityimportcheckappid=args[0]config=ServerConfiguration.config_for(appid)config.repairing=self.config.forcerepo,_cnx=repo_cnx(config)withrepo.internal_cnx()ascnx:check(repo,cnx,self.config.checks,self.config.reindex,self.config.autofix)classRebuildFTICommand(Command):"""Rebuild the full-text index of the system database of an instance. <instance> [etype(s)] the identifier of the instance to rebuild If no etype is specified, cubicweb will reindex everything, otherwise only specified etypes will be considered. """name='db-rebuild-fti'arguments='<instance>'min_args=1defrun(self,args):fromcubicweb.server.checkintegrityimportreindex_entitiesappid=args.pop(0)etypes=argsorNoneconfig=ServerConfiguration.config_for(appid)repo,cnx=repo_cnx(config)withcnx:reindex_entities(repo.schema,cnx,etypes=etypes)cnx.commit()classSynchronizeSourceCommand(Command):"""Force a source synchronization. <instance> the identifier of the instance <source> the name of the source to synchronize. """name='source-sync'arguments='<instance> <source>'min_args=max_args=2options=(('loglevel',{'short':'l','type':'choice','metavar':'<log level>','default':'info','choices':('debug','info','warning','error'),}),)defrun(self,args):fromcubicwebimportrepoapifromcubicweb.cwctlimportinit_cmdline_log_thresholdconfig=ServerConfiguration.config_for(args[0])config.global_set_option('log-file',None)config.log_format='%(levelname)s%(name)s: %(message)s'init_cmdline_log_threshold(config,self['loglevel'])repo=repoapi.get_repository(config=config)repo.hm.call_hooks('server_maintenance',repo=repo)try:try:source=repo.sources_by_uri[args[1]]exceptKeyError:raiseExecutionError('no source named %r'%args[1])withrepo.internal_cnx()ascnx:stats=source.pull_data(cnx,force=True,raise_on_error=True)finally:repo.shutdown()forkey,valinstats.items():ifval:print(key,':',val)defpermissionshandler(relation,perms):fromyams.schemaimportRelationDefinitionSchemafromyams.buildobjsimportDEFAULT_ATTRPERMSfromcubicweb.schemaimport(PUB_SYSTEM_ENTITY_PERMS,PUB_SYSTEM_REL_PERMS,PUB_SYSTEM_ATTR_PERMS,RO_REL_PERMS,RO_ATTR_PERMS)defaultrelperms=(DEFAULT_ATTRPERMS,PUB_SYSTEM_REL_PERMS,PUB_SYSTEM_ATTR_PERMS,RO_REL_PERMS,RO_ATTR_PERMS)defaulteperms=(PUB_SYSTEM_ENTITY_PERMS,)# canonicalize vs str/unicodeforpin('read','add','update','delete'):rule=perms.get(p)ifrule:perms[p]=tuple(str(x)ifisinstance(x,string_types)elsexforxinrule)returnperms,permsindefaultrelpermsorpermsindefaultepermsclassSchemaDiffCommand(Command):"""Generate a diff between schema and fsschema description. <instance> the identifier of the instance <diff-tool> the name of the diff tool to compare the two generated files. """name='schema-diff'arguments='<instance> <diff-tool>'min_args=max_args=2defrun(self,args):fromyams.diffimportschema_difffromcubicwebimportrepoapiappid=args.pop(0)diff_tool=args.pop(0)config=ServerConfiguration.config_for(appid)repo=repoapi.get_repository(config=config)fsschema=config.load_schema(expand_cubes=True)schema_diff(fsschema,repo.schema,permissionshandler,diff_tool,ignore=('eid',))forcmdclassin(CreateInstanceDBCommand,InitInstanceCommand,GrantUserOnInstanceCommand,ResetAdminPasswordCommand,DBDumpCommand,DBRestoreCommand,DBCopyCommand,AddSourceCommand,CheckRepositoryCommand,RebuildFTICommand,SynchronizeSourceCommand,SchemaDiffCommand,):CWCTL.register(cmdclass)# extend configure command to set options in sources config file ###############db_options=(('db',{'short':'d','type':'named','metavar':'[section1.]key1:value1,[section2.]key2:value2','default':None,'help':'''set <key> in <section> to <value> in "source" configuration file. If <section> is not specified, it defaults to "system".Beware that changing admin.login or admin.password using this commandwill NOT update the database with new admin credentials. Use thereset-admin-pwd command instead.''',}),)ConfigureInstanceCommand.options=merge_options(ConfigureInstanceCommand.options+db_options)configure_instance=ConfigureInstanceCommand.configure_instancedefconfigure_instance2(self,appid):configure_instance(self,appid)ifself.config.dbisnotNone:appcfg=ServerConfiguration.config_for(appid)srccfg=appcfg.read_sources_file()forkey,valueinself.config.db.items():if'.'inkey:section,key=key.split('.',1)else:section='system'try:srccfg[section][key]=valueexceptKeyError:raiseConfigurationError('unknown configuration key "%s" in section "%s" for source'%(key,section))admcfg=Configuration(options=USER_OPTIONS)admcfg['login']=srccfg['admin']['login']admcfg['password']=srccfg['admin']['password']srccfg['admin']=admcfgappcfg.write_sources_file(srccfg)ConfigureInstanceCommand.configure_instance=configure_instance2