24 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash |
24 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash |
25 # completion). So import locally in command helpers. |
25 # completion). So import locally in command helpers. |
26 import sys |
26 import sys |
27 import os |
27 import os |
28 from contextlib import contextmanager |
28 from contextlib import contextmanager |
29 import logging |
|
30 import subprocess |
|
31 |
29 |
32 from six import string_types |
30 from six import string_types |
33 from six.moves import input |
31 from six.moves import input |
34 |
32 |
35 from logilab.common import nullobject |
|
36 from logilab.common.configuration import Configuration, merge_options |
33 from logilab.common.configuration import Configuration, merge_options |
37 from logilab.common.shellutils import ASK, generate_password |
34 from logilab.common.shellutils import ASK, generate_password |
38 |
35 |
39 from logilab.database import get_db_helper, get_connection |
36 from logilab.database import get_db_helper, get_connection |
40 |
37 |
43 from cubicweb.cwctl import CWCTL, check_options_consistency, ConfigureInstanceCommand |
40 from cubicweb.cwctl import CWCTL, check_options_consistency, ConfigureInstanceCommand |
44 from cubicweb.server import SOURCE_TYPES |
41 from cubicweb.server import SOURCE_TYPES |
45 from cubicweb.server.serverconfig import ( |
42 from cubicweb.server.serverconfig import ( |
46 USER_OPTIONS, ServerConfiguration, SourceConfiguration, |
43 USER_OPTIONS, ServerConfiguration, SourceConfiguration, |
47 ask_source_config, generate_source_config) |
44 ask_source_config, generate_source_config) |
|
45 |
48 |
46 |
49 # utility functions ########################################################### |
47 # utility functions ########################################################### |
50 |
48 |
51 def source_cnx(source, dbname=None, special_privs=False, interactive=True): |
49 def source_cnx(source, dbname=None, special_privs=False, interactive=True): |
52 """open and return a connection to the system database defined in the |
50 """open and return a connection to the system database defined in the |
100 from logilab.database import _SimpleConnectionWrapper |
98 from logilab.database import _SimpleConnectionWrapper |
101 cnx = _SimpleConnectionWrapper(cnx) |
99 cnx = _SimpleConnectionWrapper(cnx) |
102 cnx.logged_user = user |
100 cnx.logged_user = user |
103 return cnx |
101 return cnx |
104 |
102 |
|
103 |
105 def system_source_cnx(source, dbms_system_base=False, |
104 def system_source_cnx(source, dbms_system_base=False, |
106 special_privs='CREATE/DROP DATABASE', interactive=True): |
105 special_privs='CREATE/DROP DATABASE', interactive=True): |
107 """shortcut to get a connextion to the instance system database |
106 """shortcut to get a connextion to the instance system database |
108 defined in the given config. If <dbms_system_base> is True, |
107 defined in the given config. If <dbms_system_base> is True, |
109 connect to the dbms system database instead (for task such as |
108 connect to the dbms system database instead (for task such as |
114 return source_cnx(source, system_db, special_privs=special_privs, |
113 return source_cnx(source, system_db, special_privs=special_privs, |
115 interactive=interactive) |
114 interactive=interactive) |
116 return source_cnx(source, special_privs=special_privs, |
115 return source_cnx(source, special_privs=special_privs, |
117 interactive=interactive) |
116 interactive=interactive) |
118 |
117 |
|
118 |
119 def _db_sys_cnx(source, special_privs, interactive=True): |
119 def _db_sys_cnx(source, special_privs, interactive=True): |
120 """return a connection on the RDMS system table (to create/drop a user or a |
120 """return a connection on the RDMS system table (to create/drop a user or a |
121 database) |
121 database) |
122 """ |
122 """ |
123 import logilab.common as lgp |
123 import logilab.common as lgp |
124 lgp.USE_MX_DATETIME = False |
124 lgp.USE_MX_DATETIME = False |
125 driver = source['db-driver'] |
|
126 helper = get_db_helper(driver) |
|
127 # connect on the dbms system base to create our base |
125 # connect on the dbms system base to create our base |
128 cnx = system_source_cnx(source, True, special_privs=special_privs, |
126 cnx = system_source_cnx(source, True, special_privs=special_privs, |
129 interactive=interactive) |
127 interactive=interactive) |
130 # disable autocommit (isolation_level(1)) because DROP and |
128 # disable autocommit (isolation_level(1)) because DROP and |
131 # CREATE DATABASE can't be executed in a transaction |
129 # CREATE DATABASE can't be executed in a transaction |
132 set_isolation_level = getattr(cnx, 'set_isolation_level', None) |
130 set_isolation_level = getattr(cnx, 'set_isolation_level', None) |
133 if set_isolation_level is not None: |
131 if set_isolation_level is not None: |
134 # set_isolation_level() is psycopg specific |
132 # set_isolation_level() is psycopg specific |
135 set_isolation_level(0) |
133 set_isolation_level(0) |
136 return cnx |
134 return cnx |
|
135 |
137 |
136 |
138 def repo_cnx(config): |
137 def repo_cnx(config): |
139 """return a in-memory repository and a repoapi connection to it""" |
138 """return a in-memory repository and a repoapi connection to it""" |
140 from cubicweb import repoapi |
139 from cubicweb import repoapi |
141 from cubicweb.server.utils import manager_userpasswd |
140 from cubicweb.server.utils import manager_userpasswd |
168 """ |
167 """ |
169 config = self.config |
168 config = self.config |
170 if not automatic: |
169 if not automatic: |
171 print(underline_title('Configuring the repository')) |
170 print(underline_title('Configuring the repository')) |
172 config.input_config('email', inputlevel) |
171 config.input_config('email', inputlevel) |
173 print('\n'+underline_title('Configuring the sources')) |
172 print('\n' + underline_title('Configuring the sources')) |
174 sourcesfile = config.sources_file() |
|
175 # hack to make Method('default_instance_id') usable in db option defs |
173 # hack to make Method('default_instance_id') usable in db option defs |
176 # (in native.py) |
174 # (in native.py) |
177 sconfig = SourceConfiguration(config, |
175 sconfig = SourceConfiguration(config, |
178 options=SOURCE_TYPES['native'].options) |
176 options=SOURCE_TYPES['native'].options) |
179 if not automatic: |
177 if not automatic: |
247 helper = get_db_helper(source['db-driver']) |
245 helper = get_db_helper(source['db-driver']) |
248 helper.drop_schema(cursor, db_namespace) |
246 helper.drop_schema(cursor, db_namespace) |
249 print('-> database schema %s dropped' % db_namespace) |
247 print('-> database schema %s dropped' % db_namespace) |
250 |
248 |
251 def _drop_database(self, source): |
249 def _drop_database(self, source): |
252 dbname = source['db-name'] |
|
253 if source['db-driver'] == 'sqlite': |
250 if source['db-driver'] == 'sqlite': |
254 print('deleting database file %(db-name)s' % source) |
251 print('deleting database file %(db-name)s' % source) |
255 os.unlink(source['db-name']) |
252 os.unlink(source['db-name']) |
256 print('-> database %(db-name)s dropped.' % source) |
253 print('-> database %(db-name)s dropped.' % source) |
257 else: |
254 else: |
258 helper = get_db_helper(source['db-driver']) |
|
259 with db_sys_transaction(source, privilege='DROP DATABASE') as cursor: |
255 with db_sys_transaction(source, privilege='DROP DATABASE') as cursor: |
260 print('dropping database %(db-name)s' % source) |
256 print('dropping database %(db-name)s' % source) |
261 cursor.execute('DROP DATABASE "%(db-name)s"' % source) |
257 cursor.execute('DROP DATABASE "%(db-name)s"' % source) |
262 print('-> database %(db-name)s dropped.' % source) |
258 print('-> database %(db-name)s dropped.' % source) |
263 |
259 |
324 name = 'db-create' |
320 name = 'db-create' |
325 arguments = '<instance>' |
321 arguments = '<instance>' |
326 min_args = max_args = 1 |
322 min_args = max_args = 1 |
327 options = ( |
323 options = ( |
328 ('automatic', |
324 ('automatic', |
329 {'short': 'a', 'action' : 'store_true', |
325 {'short': 'a', 'action': 'store_true', |
330 'default': False, |
326 'default': False, |
331 'help': 'automatic mode: never ask and use default answer to every ' |
327 'help': 'automatic mode: never ask and use default answer to every ' |
332 'question. this may require that your login match a database super ' |
328 'question. this may require that your login match a database super ' |
333 'user (allowed to create database & all).', |
329 'user (allowed to create database & all).', |
334 }), |
330 }), |
335 ('config-level', |
331 ('config-level', |
336 {'short': 'l', 'type' : 'int', 'metavar': '<level>', |
332 {'short': 'l', 'type': 'int', 'metavar': '<level>', |
337 'default': 0, |
333 'default': 0, |
338 'help': 'configuration level (0..2): 0 will ask for essential ' |
334 'help': 'configuration level (0..2): 0 will ask for essential ' |
339 'configuration parameters only while 2 will ask for all parameters', |
335 'configuration parameters only while 2 will ask for all parameters', |
340 }), |
336 }), |
341 ('create-db', |
337 ('create-db', |
342 {'short': 'c', 'type': 'yn', 'metavar': '<y or n>', |
338 {'short': 'c', 'type': 'yn', 'metavar': '<y or n>', |
343 'default': True, |
339 'default': True, |
344 'help': 'create the database (yes by default)' |
340 'help': 'create the database (yes by default)' |
345 }), |
341 }), |
346 ) |
342 ) |
347 |
343 |
348 def run(self, args): |
344 def run(self, args): |
349 """run the command with its specific arguments""" |
345 """run the command with its specific arguments""" |
350 check_options_consistency(self.config) |
346 check_options_consistency(self.config) |
351 automatic = self.get('automatic') |
347 automatic = self.get('automatic') |
355 dbname = source['db-name'] |
351 dbname = source['db-name'] |
356 driver = source['db-driver'] |
352 driver = source['db-driver'] |
357 helper = get_db_helper(driver) |
353 helper = get_db_helper(driver) |
358 if driver == 'sqlite': |
354 if driver == 'sqlite': |
359 if os.path.exists(dbname) and ( |
355 if os.path.exists(dbname) and ( |
360 automatic or |
356 automatic or |
361 ASK.confirm('Database %s already exists. Drop it?' % dbname)): |
357 ASK.confirm('Database %s already exists. Drop it?' % dbname)): |
362 os.unlink(dbname) |
358 os.unlink(dbname) |
363 elif self.config.create_db: |
359 elif self.config.create_db: |
364 print('\n'+underline_title('Creating the system database')) |
360 print('\n' + underline_title('Creating the system database')) |
365 # connect on the dbms system base to create our base |
361 # connect on the dbms system base to create our base |
366 dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER', |
362 dbcnx = _db_sys_cnx(source, 'CREATE/DROP DATABASE and / or USER', |
367 interactive=not automatic) |
363 interactive=not automatic) |
368 cursor = dbcnx.cursor() |
364 cursor = dbcnx.cursor() |
369 try: |
365 try: |
370 if helper.users_support: |
366 if helper.users_support: |
371 user = source['db-user'] |
367 user = source['db-user'] |
372 if not helper.user_exists(cursor, user) and (automatic or \ |
368 if not helper.user_exists(cursor, user) and ( |
373 ASK.confirm('Create db user %s ?' % user, default_is_yes=False)): |
369 automatic or |
|
370 ASK.confirm('Create db user %s ?' % user, default_is_yes=False)): |
374 helper.create_user(source['db-user'], source.get('db-password')) |
371 helper.create_user(source['db-user'], source.get('db-password')) |
375 print('-> user %s created.' % user) |
372 print('-> user %s created.' % user) |
376 if dbname in helper.list_databases(cursor): |
373 if dbname in helper.list_databases(cursor): |
377 if automatic or ASK.confirm('Database %s already exists -- do you want to drop it ?' % dbname): |
374 if automatic or ASK.confirm('Database %s already exists -- ' |
|
375 'do you want to drop it ?' % dbname): |
378 cursor.execute('DROP DATABASE "%s"' % dbname) |
376 cursor.execute('DROP DATABASE "%s"' % dbname) |
379 else: |
377 else: |
380 print('you may want to run "cubicweb-ctl db-init ' |
378 print('you may want to run "cubicweb-ctl db-init ' |
381 '--drop %s" manually to continue.' % config.appid) |
379 '--drop %s" manually to continue.' % config.appid) |
382 return |
380 return |
403 if automatic or ASK.confirm('Create language %s ?' % extlang): |
401 if automatic or ASK.confirm('Create language %s ?' % extlang): |
404 try: |
402 try: |
405 helper.create_language(cursor, extlang) |
403 helper.create_language(cursor, extlang) |
406 except Exception as exc: |
404 except Exception as exc: |
407 print('-> ERROR:', exc) |
405 print('-> ERROR:', exc) |
408 print('-> could not create language %s, some stored procedures might be unusable' % extlang) |
406 print('-> could not create language %s, ' |
|
407 'some stored procedures might be unusable' % extlang) |
409 cnx.rollback() |
408 cnx.rollback() |
410 else: |
409 else: |
411 cnx.commit() |
410 cnx.commit() |
412 print('-> database for instance %s created and necessary extensions installed.' % appid) |
411 print('-> database for instance %s created and necessary extensions installed.' % appid) |
413 print() |
412 print() |
450 {'short': 'd', 'action': 'store_true', |
449 {'short': 'd', 'action': 'store_true', |
451 'default': False, |
450 'default': False, |
452 'help': 'insert drop statements to remove previously existant ' |
451 'help': 'insert drop statements to remove previously existant ' |
453 'tables, indexes... (no by default)' |
452 'tables, indexes... (no by default)' |
454 }), |
453 }), |
455 ) |
454 ) |
456 |
455 |
457 def run(self, args): |
456 def run(self, args): |
458 check_options_consistency(self.config) |
457 check_options_consistency(self.config) |
459 print('\n'+underline_title('Initializing the system database')) |
458 print('\n' + underline_title('Initializing the system database')) |
460 from cubicweb.server import init_repository |
459 from cubicweb.server import init_repository |
461 appid = args[0] |
460 appid = args[0] |
462 config = ServerConfiguration.config_for(appid) |
461 config = ServerConfiguration.config_for(appid) |
463 try: |
462 try: |
464 system = config.system_source_config |
463 system = config.system_source_config |
469 host=system.get('db-host'), port=system.get('db-port'), |
468 host=system.get('db-host'), port=system.get('db-port'), |
470 user=system.get('db-user') or '', password=system.get('db-password') or '', |
469 user=system.get('db-user') or '', password=system.get('db-password') or '', |
471 schema=system.get('db-namespace'), **extra) |
470 schema=system.get('db-namespace'), **extra) |
472 except Exception as ex: |
471 except Exception as ex: |
473 raise ConfigurationError( |
472 raise ConfigurationError( |
474 'You seem to have provided wrong connection information in '\ |
473 'You seem to have provided wrong connection information in ' |
475 'the %s file. Resolve this first (error: %s).' |
474 'the %s file. Resolve this first (error: %s).' |
476 % (config.sources_file(), str(ex).strip())) |
475 % (config.sources_file(), str(ex).strip())) |
477 init_repository(config, drop=self.config.drop) |
476 init_repository(config, drop=self.config.drop) |
478 if not self.config.automatic: |
477 if not self.config.automatic: |
479 while ASK.confirm('Enter another source ?', default_is_yes=False): |
478 while ASK.confirm('Enter another source ?', default_is_yes=False): |
506 with cnx: |
505 with cnx: |
507 used = set(n for n, in cnx.execute('Any SN WHERE S is CWSource, S name SN')) |
506 used = set(n for n, in cnx.execute('Any SN WHERE S is CWSource, S name SN')) |
508 cubes = repo.get_cubes() |
507 cubes = repo.get_cubes() |
509 while True: |
508 while True: |
510 type = input('source type (%s): ' |
509 type = input('source type (%s): ' |
511 % ', '.join(sorted(SOURCE_TYPES))) |
510 % ', '.join(sorted(SOURCE_TYPES))) |
512 if type not in SOURCE_TYPES: |
511 if type not in SOURCE_TYPES: |
513 print('-> unknown source type, use one of the available types.') |
512 print('-> unknown source type, use one of the available types.') |
514 continue |
513 continue |
515 sourcemodule = SOURCE_TYPES[type].module |
514 sourcemodule = SOURCE_TYPES[type].module |
516 if not sourcemodule.startswith('cubicweb.'): |
515 if not sourcemodule.startswith('cubicweb.'): |
517 # module names look like cubes.mycube.themodule |
516 # module names look like cubes.mycube.themodule |
518 sourcecube = SOURCE_TYPES[type].module.split('.', 2)[1] |
517 sourcecube = SOURCE_TYPES[type].module.split('.', 2)[1] |
519 # if the source adapter is coming from an external component, |
518 # if the source adapter is coming from an external component, |
520 # ensure it's specified in used cubes |
519 # ensure it's specified in used cubes |
521 if not sourcecube in cubes: |
520 if sourcecube not in cubes: |
522 print ('-> this source type require the %s cube which is ' |
521 print ('-> this source type require the %s cube which is ' |
523 'not used by the instance.') |
522 'not used by the instance.') |
524 continue |
523 continue |
525 break |
524 break |
526 while True: |
525 while True: |
527 parser = input('parser type (%s): ' |
526 parser = input('parser type (%s): ' |
528 % ', '.join(sorted(repo.vreg['parsers']))) |
527 % ', '.join(sorted(repo.vreg['parsers']))) |
529 if parser in repo.vreg['parsers']: |
528 if parser in repo.vreg['parsers']: |
530 break |
529 break |
531 print('-> unknown parser identifier, use one of the available types.') |
530 print('-> unknown parser identifier, use one of the available types.') |
532 while True: |
531 while True: |
533 sourceuri = input('source identifier (a unique name used to ' |
532 sourceuri = input('source identifier (a unique name used to ' |
534 'tell sources apart): ').strip() |
533 'tell sources apart): ').strip() |
535 if not sourceuri: |
534 if not sourceuri: |
536 print('-> mandatory.') |
535 print('-> mandatory.') |
537 else: |
536 else: |
538 sourceuri = unicode(sourceuri, sys.stdin.encoding) |
537 sourceuri = unicode(sourceuri, sys.stdin.encoding) |
539 if sourceuri in used: |
538 if sourceuri in used: |
563 name = 'db-grant-user' |
562 name = 'db-grant-user' |
564 arguments = '<instance> <user>' |
563 arguments = '<instance> <user>' |
565 min_args = max_args = 2 |
564 min_args = max_args = 2 |
566 options = ( |
565 options = ( |
567 ('set-owner', |
566 ('set-owner', |
568 {'short': 'o', 'type' : 'yn', 'metavar' : '<yes or no>', |
567 {'short': 'o', 'type': 'yn', 'metavar': '<yes or no>', |
569 'default' : False, |
568 'default': False, |
570 'help': 'Set the user as tables owner if yes (no by default).'} |
569 'help': 'Set the user as tables owner if yes (no by default).'} |
571 ), |
570 ), |
572 ) |
571 ) |
|
572 |
573 def run(self, args): |
573 def run(self, args): |
574 """run the command with its specific arguments""" |
574 """run the command with its specific arguments""" |
575 from cubicweb.server.sqlutils import sqlexec, sqlgrants |
575 from cubicweb.server.sqlutils import sqlexec, sqlgrants |
576 appid, user = args |
576 appid, user = args |
577 config = ServerConfiguration.config_for(appid) |
577 config = ServerConfiguration.config_for(appid) |
602 name = 'reset-admin-pwd' |
602 name = 'reset-admin-pwd' |
603 arguments = '<instance>' |
603 arguments = '<instance>' |
604 min_args = max_args = 1 |
604 min_args = max_args = 1 |
605 options = ( |
605 options = ( |
606 ('password', |
606 ('password', |
607 {'short': 'p', 'type' : 'string', 'metavar' : '<new-password>', |
607 {'short': 'p', 'type': 'string', 'metavar': '<new-password>', |
608 'default' : None, |
608 'default': None, |
609 'help': 'Use this password instead of prompt for one.\n' |
609 'help': 'Use this password instead of prompt for one.\n' |
610 '/!\ THIS IS AN INSECURE PRACTICE /!\ \n' |
610 '/!\ THIS IS AN INSECURE PRACTICE /!\ \n' |
611 'the password will appear in shell history'} |
611 'the password will appear in shell history'} |
612 ), |
612 ), |
613 ) |
613 ) |
614 |
614 |
615 def run(self, args): |
615 def run(self, args): |
616 """run the command with its specific arguments""" |
616 """run the command with its specific arguments""" |
617 from cubicweb.server.utils import crypt_password, manager_userpasswd |
617 from cubicweb.server.utils import crypt_password, manager_userpasswd |
618 appid = args[0] |
618 appid = args[0] |
659 cnx.commit() |
659 cnx.commit() |
660 print('-> password reset, sources file regenerated.') |
660 print('-> password reset, sources file regenerated.') |
661 cnx.close() |
661 cnx.close() |
662 |
662 |
663 |
663 |
664 |
|
665 def _remote_dump(host, appid, output, sudo=False): |
664 def _remote_dump(host, appid, output, sudo=False): |
666 # XXX generate unique/portable file name |
665 # XXX generate unique/portable file name |
667 from datetime import date |
666 from datetime import date |
668 filename = '%s-%s.tgz' % (appid, date.today().strftime('%Y-%m-%d')) |
667 filename = '%s-%s.tgz' % (appid, date.today().strftime('%Y-%m-%d')) |
669 dmpcmd = 'cubicweb-ctl db-dump -o /tmp/%s %s' % (filename, appid) |
668 dmpcmd = 'cubicweb-ctl db-dump -o /tmp/%s %s' % (filename, appid) |
680 if os.system(cmd): |
679 if os.system(cmd): |
681 raise ExecutionError('Error while retrieving the dump at /tmp/%s' % filename) |
680 raise ExecutionError('Error while retrieving the dump at /tmp/%s' % filename) |
682 rmcmd = 'ssh -t %s "rm -f /tmp/%s"' % (host, filename) |
681 rmcmd = 'ssh -t %s "rm -f /tmp/%s"' % (host, filename) |
683 print(rmcmd) |
682 print(rmcmd) |
684 if os.system(rmcmd) and not ASK.confirm( |
683 if os.system(rmcmd) and not ASK.confirm( |
685 'An error occurred while deleting remote dump at /tmp/%s. ' |
684 'An error occurred while deleting remote dump at /tmp/%s. ' |
686 'Continue anyway?' % filename): |
685 'Continue anyway?' % filename): |
687 raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename) |
686 raise ExecutionError('Error while deleting remote dump at /tmp/%s' % filename) |
688 |
687 |
689 |
688 |
690 def _local_dump(appid, output, format='native'): |
689 def _local_dump(appid, output, format='native'): |
691 config = ServerConfiguration.config_for(appid) |
690 config = ServerConfiguration.config_for(appid) |
692 config.quick_start = True |
691 config.quick_start = True |
693 mih = config.migration_handler(verbosity=1) |
692 mih = config.migration_handler(verbosity=1) |
694 mih.backup_database(output, askconfirm=False, format=format) |
693 mih.backup_database(output, askconfirm=False, format=format) |
695 mih.shutdown() |
694 mih.shutdown() |
696 |
695 |
|
696 |
697 def _local_restore(appid, backupfile, drop, format='native'): |
697 def _local_restore(appid, backupfile, drop, format='native'): |
698 config = ServerConfiguration.config_for(appid) |
698 config = ServerConfiguration.config_for(appid) |
699 config.verbosity = 1 # else we won't be asked for confirmation on problems |
699 config.verbosity = 1 # else we won't be asked for confirmation on problems |
700 config.quick_start = True |
700 config.quick_start = True |
701 mih = config.migration_handler(connect=False, verbosity=1) |
701 mih = config.migration_handler(connect=False, verbosity=1) |
702 mih.restore_database(backupfile, drop, askconfirm=False, format=format) |
702 mih.restore_database(backupfile, drop, askconfirm=False, format=format) |
703 repo = mih.repo |
703 repo = mih.repo |
704 # version of the database |
704 # version of the database |
724 print("** 'cubicweb-ctl upgrade %s'" % config.appid) |
724 print("** 'cubicweb-ctl upgrade %s'" % config.appid) |
725 return |
725 return |
726 # * database version = installed software, database version = instance fs version |
726 # * database version = installed software, database version = instance fs version |
727 # ok! |
727 # ok! |
728 |
728 |
|
729 |
729 def instance_status(config, cubicwebapplversion, vcconf): |
730 def instance_status(config, cubicwebapplversion, vcconf): |
730 cubicwebversion = config.cubicweb_version() |
731 cubicwebversion = config.cubicweb_version() |
731 if cubicwebapplversion > cubicwebversion: |
732 if cubicwebapplversion > cubicwebversion: |
732 return 'needsoftupgrade' |
733 return 'needsoftupgrade' |
733 if cubicwebapplversion < cubicwebversion: |
734 if cubicwebapplversion < cubicwebversion: |
734 return 'needapplupgrade' |
735 return 'needapplupgrade' |
735 for cube in config.cubes(): |
736 for cube in config.cubes(): |
736 try: |
737 try: |
737 softversion = config.cube_version(cube) |
738 softversion = config.cube_version(cube) |
738 except ConfigurationError: |
739 except ConfigurationError: |
739 print('-> Error: no cube version information for %s, please check that the cube is installed.' % cube) |
740 print('-> Error: no cube version information for %s, ' |
|
741 'please check that the cube is installed.' % cube) |
740 continue |
742 continue |
741 try: |
743 try: |
742 applversion = vcconf[cube] |
744 applversion = vcconf[cube] |
743 except KeyError: |
745 except KeyError: |
744 print('-> Error: no cube version information for %s in version configuration.' % cube) |
746 print('-> Error: no cube version information for %s in version configuration.' % cube) |
762 name = 'db-dump' |
764 name = 'db-dump' |
763 arguments = '<instance>' |
765 arguments = '<instance>' |
764 min_args = max_args = 1 |
766 min_args = max_args = 1 |
765 options = ( |
767 options = ( |
766 ('output', |
768 ('output', |
767 {'short': 'o', 'type' : 'string', 'metavar' : '<file>', |
769 {'short': 'o', 'type': 'string', 'metavar': '<file>', |
768 'default' : None, |
770 'default': None, |
769 'help': 'Specify the backup file where the backup will be stored.'} |
771 'help': 'Specify the backup file where the backup will be stored.'} |
770 ), |
772 ), |
771 ('sudo', |
773 ('sudo', |
772 {'short': 's', 'action' : 'store_true', |
774 {'short': 's', 'action': 'store_true', |
773 'default' : False, |
775 'default': False, |
774 'help': 'Use sudo on the remote host.'} |
776 'help': 'Use sudo on the remote host.'} |
775 ), |
777 ), |
776 ('format', |
778 ('format', |
777 {'short': 'f', 'default': 'native', 'type': 'choice', |
779 {'short': 'f', 'default': 'native', 'type': 'choice', |
778 'choices': ('native', 'portable'), |
780 'choices': ('native', 'portable'), |
779 'help': '"native" format uses db backend utilities to dump the database. ' |
781 'help': '"native" format uses db backend utilities to dump the database. ' |
780 '"portable" format uses a database independent format'} |
782 '"portable" format uses a database independent format'} |
781 ), |
783 ), |
782 ) |
784 ) |
783 |
785 |
784 def run(self, args): |
786 def run(self, args): |
785 appid = args[0] |
787 appid = args[0] |
786 if ':' in appid: |
788 if ':' in appid: |
787 host, appid = appid.split(':') |
789 host, appid = appid.split(':') |
788 _remote_dump(host, appid, self.config.output, self.config.sudo) |
790 _remote_dump(host, appid, self.config.output, self.config.sudo) |
789 else: |
791 else: |
790 _local_dump(appid, self.config.output, format=self.config.format) |
792 _local_dump(appid, self.config.output, format=self.config.format) |
791 |
793 |
792 |
794 |
793 |
|
794 |
|
795 class DBRestoreCommand(Command): |
795 class DBRestoreCommand(Command): |
796 """Restore the system database of an instance. |
796 """Restore the system database of an instance. |
797 |
797 |
798 <instance> |
798 <instance> |
799 the identifier of the instance to restore |
799 the identifier of the instance to restore |
802 arguments = '<instance> <backupfile>' |
802 arguments = '<instance> <backupfile>' |
803 min_args = max_args = 2 |
803 min_args = max_args = 2 |
804 |
804 |
805 options = ( |
805 options = ( |
806 ('no-drop', |
806 ('no-drop', |
807 {'short': 'n', 'action' : 'store_true', 'default' : False, |
807 {'short': 'n', 'action': 'store_true', 'default': False, |
808 'help': 'for some reason the database doesn\'t exist and so ' |
808 'help': 'for some reason the database doesn\'t exist and so ' |
809 'should not be dropped.'} |
809 'should not be dropped.'} |
810 ), |
810 ), |
811 ('format', |
811 ('format', |
812 {'short': 'f', 'default': 'native', 'type': 'choice', |
812 {'short': 'f', 'default': 'native', 'type': 'choice', |
813 'choices': ('native', 'portable'), |
813 'choices': ('native', 'portable'), |
814 'help': 'the format used when dumping the database'}), |
814 'help': 'the format used when dumping the database'}), |
815 ) |
815 ) |
816 |
816 |
817 def run(self, args): |
817 def run(self, args): |
818 appid, backupfile = args |
818 appid, backupfile = args |
819 if self.config.format == 'portable': |
819 if self.config.format == 'portable': |
820 # we need to ensure a DB exist before restoring from portable format |
820 # we need to ensure a DB exist before restoring from portable format |
849 name = 'db-copy' |
849 name = 'db-copy' |
850 arguments = '<src-instance> <dest-instance>' |
850 arguments = '<src-instance> <dest-instance>' |
851 min_args = max_args = 2 |
851 min_args = max_args = 2 |
852 options = ( |
852 options = ( |
853 ('no-drop', |
853 ('no-drop', |
854 {'short': 'n', 'action' : 'store_true', |
854 {'short': 'n', 'action': 'store_true', |
855 'default' : False, |
855 'default': False, |
856 'help': 'For some reason the database doesn\'t exist and so ' |
856 'help': 'For some reason the database doesn\'t exist and so ' |
857 'should not be dropped.'} |
857 'should not be dropped.'} |
858 ), |
858 ), |
859 ('keep-dump', |
859 ('keep-dump', |
860 {'short': 'k', 'action' : 'store_true', |
860 {'short': 'k', 'action': 'store_true', |
861 'default' : False, |
861 'default': False, |
862 'help': 'Specify that the dump file should not be automatically removed.'} |
862 'help': 'Specify that the dump file should not be automatically removed.'} |
863 ), |
863 ), |
864 ('sudo', |
864 ('sudo', |
865 {'short': 's', 'action' : 'store_true', |
865 {'short': 's', 'action': 'store_true', |
866 'default' : False, |
866 'default': False, |
867 'help': 'Use sudo on the remote host.'} |
867 'help': 'Use sudo on the remote host.'} |
868 ), |
868 ), |
869 ('format', |
869 ('format', |
870 {'short': 'f', 'default': 'native', 'type': 'choice', |
870 {'short': 'f', 'default': 'native', 'type': 'choice', |
871 'choices': ('native', 'portable'), |
871 'choices': ('native', 'portable'), |
872 'help': '"native" format uses db backend utilities to dump the database. ' |
872 'help': '"native" format uses db backend utilities to dump the database. ' |
873 '"portable" format uses a database independent format'} |
873 '"portable" format uses a database independent format'} |
874 ), |
874 ), |
875 ) |
875 ) |
876 |
876 |
877 def run(self, args): |
877 def run(self, args): |
878 import tempfile |
878 import tempfile |
879 srcappid, destappid = args |
879 srcappid, destappid = args |
880 fd, output = tempfile.mkstemp() |
880 fd, output = tempfile.mkstemp() |
901 name = 'db-check' |
901 name = 'db-check' |
902 arguments = '<instance>' |
902 arguments = '<instance>' |
903 min_args = max_args = 1 |
903 min_args = max_args = 1 |
904 options = ( |
904 options = ( |
905 ('checks', |
905 ('checks', |
906 {'short': 'c', 'type' : 'csv', 'metavar' : '<check list>', |
906 {'short': 'c', 'type': 'csv', 'metavar': '<check list>', |
907 'default' : ('entities', 'relations', |
907 'default': ('entities', 'relations', |
908 'mandatory_relations', 'mandatory_attributes', |
908 'mandatory_relations', 'mandatory_attributes', |
909 'metadata', 'schema', 'text_index'), |
909 'metadata', 'schema', 'text_index'), |
910 'help': 'Comma separated list of check to run. By default run all \ |
910 'help': 'Comma separated list of check to run. By default run all \ |
911 checks, i.e. entities, relations, mandatory_relations, mandatory_attributes, \ |
911 checks, i.e. entities, relations, mandatory_relations, mandatory_attributes, \ |
912 metadata, text_index and schema.'} |
912 metadata, text_index and schema.'} |
913 ), |
913 ), |
914 |
914 |
915 ('autofix', |
915 ('autofix', |
916 {'short': 'a', 'type' : 'yn', 'metavar' : '<yes or no>', |
916 {'short': 'a', 'type': 'yn', 'metavar': '<yes or no>', |
917 'default' : False, |
917 'default': False, |
918 'help': 'Automatically correct integrity problems if this option \ |
918 'help': 'Automatically correct integrity problems if this option \ |
919 is set to "y" or "yes", else only display them'} |
919 is set to "y" or "yes", else only display them'} |
920 ), |
920 ), |
921 ('reindex', |
921 ('reindex', |
922 {'short': 'r', 'type' : 'yn', 'metavar' : '<yes or no>', |
922 {'short': 'r', 'type': 'yn', 'metavar': '<yes or no>', |
923 'default' : False, |
923 'default': False, |
924 'help': 're-indexes the database for full text search if this \ |
924 'help': 're-indexes the database for full text search if this \ |
925 option is set to "y" or "yes" (may be long for large database).'} |
925 option is set to "y" or "yes" (may be long for large database).'} |
926 ), |
926 ), |
927 ('force', |
927 ('force', |
928 {'short': 'f', 'action' : 'store_true', |
928 {'short': 'f', 'action': 'store_true', |
929 'default' : False, |
929 'default': False, |
930 'help': 'don\'t check instance is up to date.'} |
930 'help': 'don\'t check instance is up to date.'} |
931 ), |
931 ), |
932 |
932 ) |
933 ) |
|
934 |
933 |
935 def run(self, args): |
934 def run(self, args): |
936 from cubicweb.server.checkintegrity import check |
935 from cubicweb.server.checkintegrity import check |
937 appid = args[0] |
936 appid = args[0] |
938 config = ServerConfiguration.config_for(appid) |
937 config = ServerConfiguration.config_for(appid) |
979 """ |
978 """ |
980 name = 'source-sync' |
979 name = 'source-sync' |
981 arguments = '<instance> <source>' |
980 arguments = '<instance> <source>' |
982 min_args = max_args = 2 |
981 min_args = max_args = 2 |
983 options = ( |
982 options = ( |
984 ('loglevel', |
983 ('loglevel', |
985 {'short': 'l', 'type' : 'choice', 'metavar': '<log level>', |
984 {'short': 'l', 'type': 'choice', 'metavar': '<log level>', |
986 'default': 'info', 'choices': ('debug', 'info', 'warning', 'error'), |
985 'default': 'info', 'choices': ('debug', 'info', 'warning', 'error')}, |
987 }), |
986 ), |
988 ) |
987 ) |
989 |
988 |
990 def run(self, args): |
989 def run(self, args): |
991 from cubicweb import repoapi |
990 from cubicweb import repoapi |
992 from cubicweb.cwctl import init_cmdline_log_threshold |
991 from cubicweb.cwctl import init_cmdline_log_threshold |
1008 for key, val in stats.items(): |
1007 for key, val in stats.items(): |
1009 if val: |
1008 if val: |
1010 print(key, ':', val) |
1009 print(key, ':', val) |
1011 |
1010 |
1012 |
1011 |
1013 |
|
1014 def permissionshandler(relation, perms): |
1012 def permissionshandler(relation, perms): |
1015 from yams.schema import RelationDefinitionSchema |
|
1016 from yams.buildobjs import DEFAULT_ATTRPERMS |
1013 from yams.buildobjs import DEFAULT_ATTRPERMS |
1017 from cubicweb.schema import (PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, |
1014 from cubicweb.schema import (PUB_SYSTEM_ENTITY_PERMS, PUB_SYSTEM_REL_PERMS, |
1018 PUB_SYSTEM_ATTR_PERMS, RO_REL_PERMS, RO_ATTR_PERMS) |
1015 PUB_SYSTEM_ATTR_PERMS, RO_REL_PERMS, RO_ATTR_PERMS) |
1019 defaultrelperms = (DEFAULT_ATTRPERMS, PUB_SYSTEM_REL_PERMS, |
1016 defaultrelperms = (DEFAULT_ATTRPERMS, PUB_SYSTEM_REL_PERMS, |
1020 PUB_SYSTEM_ATTR_PERMS, RO_REL_PERMS, RO_ATTR_PERMS) |
1017 PUB_SYSTEM_ATTR_PERMS, RO_REL_PERMS, RO_ATTR_PERMS) |
1061 |
1058 |
1062 # extend configure command to set options in sources config file ############### |
1059 # extend configure command to set options in sources config file ############### |
1063 |
1060 |
1064 db_options = ( |
1061 db_options = ( |
1065 ('db', |
1062 ('db', |
1066 {'short': 'd', 'type' : 'named', 'metavar' : '[section1.]key1:value1,[section2.]key2:value2', |
1063 {'short': 'd', 'type': 'named', 'metavar': '[section1.]key1:value1,[section2.]key2:value2', |
1067 'default': None, |
1064 'default': None, |
1068 'help': '''set <key> in <section> to <value> in "source" configuration file. If <section> is not specified, it defaults to "system". |
1065 'help': '''set <key> in <section> to <value> in "source" configuration file. If |
|
1066 <section> is not specified, it defaults to "system". |
1069 |
1067 |
1070 Beware that changing admin.login or admin.password using this command |
1068 Beware that changing admin.login or admin.password using this command |
1071 will NOT update the database with new admin credentials. Use the |
1069 will NOT update the database with new admin credentials. Use the |
1072 reset-admin-pwd command instead. |
1070 reset-admin-pwd command instead. |
1073 ''', |
1071 ''', |
1074 }), |
1072 }), |
1075 ) |
1073 ) |
1076 |
1074 |
1077 ConfigureInstanceCommand.options = merge_options( |
1075 ConfigureInstanceCommand.options = merge_options( |
1078 ConfigureInstanceCommand.options + db_options) |
1076 ConfigureInstanceCommand.options + db_options) |
1079 |
1077 |
1080 configure_instance = ConfigureInstanceCommand.configure_instance |
1078 configure_instance = ConfigureInstanceCommand.configure_instance |
|
1079 |
|
1080 |
1081 def configure_instance2(self, appid): |
1081 def configure_instance2(self, appid): |
1082 configure_instance(self, appid) |
1082 configure_instance(self, appid) |
1083 if self.config.db is not None: |
1083 if self.config.db is not None: |
1084 appcfg = ServerConfiguration.config_for(appid) |
1084 appcfg = ServerConfiguration.config_for(appid) |
1085 srccfg = appcfg.read_sources_file() |
1085 srccfg = appcfg.read_sources_file() |
1089 else: |
1089 else: |
1090 section = 'system' |
1090 section = 'system' |
1091 try: |
1091 try: |
1092 srccfg[section][key] = value |
1092 srccfg[section][key] = value |
1093 except KeyError: |
1093 except KeyError: |
1094 raise ConfigurationError('unknown configuration key "%s" in section "%s" for source' % (key, section)) |
1094 raise ConfigurationError('unknown configuration key "%s" in section "%s" for source' |
|
1095 % (key, section)) |
1095 admcfg = Configuration(options=USER_OPTIONS) |
1096 admcfg = Configuration(options=USER_OPTIONS) |
1096 admcfg['login'] = srccfg['admin']['login'] |
1097 admcfg['login'] = srccfg['admin']['login'] |
1097 admcfg['password'] = srccfg['admin']['password'] |
1098 admcfg['password'] = srccfg['admin']['password'] |
1098 srccfg['admin'] = admcfg |
1099 srccfg['admin'] = admcfg |
1099 appcfg.write_sources_file(srccfg) |
1100 appcfg.write_sources_file(srccfg) |
|
1101 |
1100 ConfigureInstanceCommand.configure_instance = configure_instance2 |
1102 ConfigureInstanceCommand.configure_instance = configure_instance2 |