diff -r 058bb3dc685f -r 0b59724cb3f2 cwctl.py --- a/cwctl.py Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1154 +0,0 @@ -# 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 . -"""the cubicweb-ctl tool, based on logilab.common.clcommands to -provide a pluggable commands system. -""" -from __future__ import print_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. -import sys -from warnings import warn, filterwarnings -from os import remove, listdir, system, pathsep -from os.path import exists, join, isfile, isdir, dirname, abspath - -try: - from os import kill, getpgid -except ImportError: - def kill(*args): - """win32 kill implementation""" - def getpgid(): - """win32 getpgid implementation""" - -from six.moves.urllib.parse import urlparse - -from logilab.common.clcommands import CommandLine -from logilab.common.shellutils import ASK -from logilab.common.configuration import merge_options - -from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage -from cubicweb.utils import support_args -from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS -from cubicweb.toolsutils import Command, rm, create_dir, underline_title -from cubicweb.__pkginfo__ import version - -# don't check duplicated commands, it occurs when reloading site_cubicweb -CWCTL = CommandLine('cubicweb-ctl', 'The CubicWeb swiss-knife.', - version=version, check_duplicated_command=False) - -def wait_process_end(pid, maxtry=10, waittime=1): - """wait for a process to actually die""" - import signal - from time import sleep - nbtry = 0 - while nbtry < maxtry: - try: - kill(pid, signal.SIGUSR1) - except (OSError, AttributeError): # XXX win32 - break - nbtry += 1 - sleep(waittime) - else: - raise ExecutionError('can\'t kill process %s' % pid) - -def list_instances(regdir): - if isdir(regdir): - return sorted(idir for idir in listdir(regdir) if isdir(join(regdir, idir))) - else: - return [] - -def detect_available_modes(templdir): - modes = [] - for fname in ('schema', 'schema.py'): - if exists(join(templdir, fname)): - modes.append('repository') - break - for fname in ('data', 'views', 'views.py'): - if exists(join(templdir, fname)): - modes.append('web ui') - break - return modes - - -class InstanceCommand(Command): - """base class for command taking 0 to n instance id as arguments - (0 meaning all registered instances) - """ - arguments = '[...]' - options = ( - ("force", - {'short': 'f', 'action' : 'store_true', - 'default': False, - 'help': 'force command without asking confirmation', - } - ), - ) - actionverb = None - - def ordered_instances(self): - """return instances in the order in which they should be started, - considering $REGISTRY_DIR/startorder file if it exists (useful when - some instances depends on another as external source). - - Instance used by another one should appears first in the file (one - instance per line) - """ - regdir = cwcfg.instances_dir() - _allinstances = list_instances(regdir) - if isfile(join(regdir, 'startorder')): - allinstances = [] - for line in open(join(regdir, 'startorder')): - line = line.strip() - if line and not line.startswith('#'): - try: - _allinstances.remove(line) - allinstances.append(line) - except ValueError: - print('ERROR: startorder file contains unexistant ' - 'instance %s' % line) - allinstances += _allinstances - else: - allinstances = _allinstances - return allinstances - - def run(self, args): - """run the _method on each argument (a list of instance - identifiers) - """ - if not args: - args = self.ordered_instances() - try: - askconfirm = not self.config.force - except AttributeError: - # no force option - askconfirm = False - else: - askconfirm = False - self.run_args(args, askconfirm) - - def run_args(self, args, askconfirm): - status = 0 - for appid in args: - if askconfirm: - print('*'*72) - if not ASK.confirm('%s instance %r ?' % (self.name, appid)): - continue - try: - status = max(status, self.run_arg(appid)) - except (KeyboardInterrupt, SystemExit): - sys.stderr.write('%s aborted\n' % self.name) - return 2 # specific error code - sys.exit(status) - - def run_arg(self, appid): - cmdmeth = getattr(self, '%s_instance' % self.name) - try: - status = cmdmeth(appid) - except (ExecutionError, ConfigurationError) as ex: - sys.stderr.write('instance %s not %s: %s\n' % ( - appid, self.actionverb, ex)) - status = 4 - except Exception as ex: - import traceback - traceback.print_exc() - sys.stderr.write('instance %s not %s: %s\n' % ( - appid, self.actionverb, ex)) - status = 8 - return status - -class InstanceCommandFork(InstanceCommand): - """Same as `InstanceCommand`, but command is forked in a new environment - for each argument - """ - - def run_args(self, args, askconfirm): - if len(args) > 1: - forkcmd = ' '.join(w for w in sys.argv if not w in args) - else: - forkcmd = None - for appid in args: - if askconfirm: - print('*'*72) - if not ASK.confirm('%s instance %r ?' % (self.name, appid)): - continue - if forkcmd: - status = system('%s %s' % (forkcmd, appid)) - if status: - print('%s exited with status %s' % (forkcmd, status)) - else: - self.run_arg(appid) - - -# base commands ############################################################### - -class ListCommand(Command): - """List configurations, cubes and instances. - - List available configurations, installed cubes, and registered instances. - - If given, the optional argument allows to restrict listing only a category of items. - """ - name = 'list' - arguments = '[all|cubes|configurations|instances]' - options = ( - ('verbose', - {'short': 'v', 'action' : 'store_true', - 'help': "display more information."}), - ) - - def run(self, args): - """run the command with its specific arguments""" - if not args: - mode = 'all' - elif len(args) == 1: - mode = args[0] - else: - raise BadCommandUsage('Too many arguments') - - from cubicweb.migration import ConfigurationProblem - - if mode == 'all': - print('CubicWeb %s (%s mode)' % (cwcfg.cubicweb_version(), cwcfg.mode)) - print() - - if mode in ('all', 'config', 'configurations'): - print('Available configurations:') - for config in CONFIGURATIONS: - print('*', config.name) - for line in config.__doc__.splitlines(): - line = line.strip() - if not line: - continue - print(' ', line) - print() - - if mode in ('all', 'cubes'): - cfgpb = ConfigurationProblem(cwcfg) - try: - cubesdir = pathsep.join(cwcfg.cubes_search_path()) - namesize = max(len(x) for x in cwcfg.available_cubes()) - except ConfigurationError as ex: - print('No cubes available:', ex) - except ValueError: - print('No cubes available in %s' % cubesdir) - else: - print('Available cubes (%s):' % cubesdir) - for cube in cwcfg.available_cubes(): - try: - tinfo = cwcfg.cube_pkginfo(cube) - tversion = tinfo.version - cfgpb.add_cube(cube, tversion) - except (ConfigurationError, AttributeError) as ex: - tinfo = None - tversion = '[missing cube information: %s]' % ex - print('* %s %s' % (cube.ljust(namesize), tversion)) - if self.config.verbose: - if tinfo: - descr = getattr(tinfo, 'description', '') - if not descr: - descr = tinfo.__doc__ - if descr: - print(' '+ ' \n'.join(descr.splitlines())) - modes = detect_available_modes(cwcfg.cube_dir(cube)) - print(' available modes: %s' % ', '.join(modes)) - print() - - if mode in ('all', 'instances'): - try: - regdir = cwcfg.instances_dir() - except ConfigurationError as ex: - print('No instance available:', ex) - print() - return - instances = list_instances(regdir) - if instances: - print('Available instances (%s):' % regdir) - for appid in instances: - modes = cwcfg.possible_configurations(appid) - if not modes: - print('* %s (BROKEN instance, no configuration found)' % appid) - continue - print('* %s (%s)' % (appid, ', '.join(modes))) - try: - config = cwcfg.config_for(appid, modes[0]) - except Exception as exc: - print(' (BROKEN instance, %s)' % exc) - continue - else: - print('No instance available in %s' % regdir) - print() - - if mode == 'all': - # configuration management problem solving - cfgpb.solve() - if cfgpb.warnings: - print('Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings)) - if cfgpb.errors: - print('Errors:') - for op, cube, version, src in cfgpb.errors: - if op == 'add': - print('* cube', cube, end=' ') - if version: - print(' version', version, end=' ') - print('is not installed, but required by %s' % src) - else: - print('* cube %s version %s is installed, but version %s is required by %s' % ( - cube, cfgpb.cubes[cube], version, src)) - -def check_options_consistency(config): - if config.automatic and config.config_level > 0: - raise BadCommandUsage('--automatic and --config-level should not be ' - 'used together') - -class CreateInstanceCommand(Command): - """Create an instance from a cube. This is a unified - command which can handle web / server / all-in-one installation - according to available parts of the software library and of the - desired cube. - - - the name of cube to use (list available cube names using - the "list" command). You can use several cubes by separating - them using comma (e.g. 'jpl,email') - - an identifier for the instance to create - """ - name = 'create' - arguments = ' ' - min_args = max_args = 2 - options = ( - ('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': '', - 'default': 0, - 'help': 'configuration level (0..2): 0 will ask for essential ' - 'configuration parameters only while 2 will ask for all parameters', - }), - ('config', - {'short': 'c', 'type' : 'choice', 'metavar': '', - 'choices': ('all-in-one', 'repository'), - 'default': 'all-in-one', - 'help': 'installation type, telling which part of an instance ' - 'should be installed. You can list available configurations using the' - ' "list" command. Default to "all-in-one", e.g. an installation ' - 'embedding both the RQL repository and the web server.', - }), - ('no-db-create', - {'short': 'S', - 'action': 'store_true', - 'default': False, - 'help': 'stop after creation and do not continue with db-create', - }), - ) - - def run(self, args): - """run the command with its specific arguments""" - from logilab.common.textutils import splitstrip - check_options_consistency(self.config) - configname = self.config.config - cubes, appid = args - cubes = splitstrip(cubes) - # get the configuration and helper - config = cwcfg.config_for(appid, configname, creating=True) - cubes = config.expand_cubes(cubes) - config.init_cubes(cubes) - helper = self.config_helper(config) - # check the cube exists - try: - templdirs = [cwcfg.cube_dir(cube) - for cube in cubes] - except ConfigurationError as ex: - print(ex) - print('\navailable cubes:', end=' ') - print(', '.join(cwcfg.available_cubes())) - return - # create the registry directory for this instance - print('\n'+underline_title('Creating the instance %s' % appid)) - create_dir(config.apphome) - # cubicweb-ctl configuration - if not self.config.automatic: - print('\n'+underline_title('Configuring the instance (%s.conf)' - % configname)) - config.input_config('main', self.config.config_level) - # configuration'specific stuff - print() - helper.bootstrap(cubes, self.config.automatic, self.config.config_level) - # input for cubes specific options - if not self.config.automatic: - sections = set(sect.lower() for sect, opt, odict in config.all_options() - if 'type' in odict - and odict.get('level') <= self.config.config_level) - for section in sections: - if section not in ('main', 'email', 'web'): - print('\n' + underline_title('%s options' % section)) - config.input_config(section, self.config.config_level) - # write down configuration - config.save() - self._handle_win32(config, appid) - print('-> generated config %s' % config.main_config_file()) - # handle i18n files structure - # in the first cube given - from cubicweb import i18n - langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))] - errors = config.i18ncompile(langs) - if errors: - print('\n'.join(errors)) - if self.config.automatic \ - or not ASK.confirm('error while compiling message catalogs, ' - 'continue anyway ?'): - print('creation not completed') - return - # create the additional data directory for this instance - if config.appdatahome != config.apphome: # true in dev mode - create_dir(config.appdatahome) - create_dir(join(config.appdatahome, 'backup')) - if config['uid']: - from logilab.common.shellutils import chown - # this directory should be owned by the uid of the server process - print('set %s as owner of the data directory' % config['uid']) - chown(config.appdatahome, config['uid']) - print('\n-> creation done for %s\n' % repr(config.apphome)[1:-1]) - if not self.config.no_db_create: - helper.postcreate(self.config.automatic, self.config.config_level) - - def _handle_win32(self, config, appid): - if sys.platform != 'win32': - return - service_template = """ -import sys -import win32serviceutil -sys.path.insert(0, r"%(CWPATH)s") - -from cubicweb.etwist.service import CWService - -classdict = {'_svc_name_': 'cubicweb-%(APPID)s', - '_svc_display_name_': 'CubicWeb ' + '%(CNAME)s', - 'instance': '%(APPID)s'} -%(CNAME)sService = type('%(CNAME)sService', (CWService,), classdict) - -if __name__ == '__main__': - win32serviceutil.HandleCommandLine(%(CNAME)sService) -""" - open(join(config.apphome, 'win32svc.py'), 'wb').write( - service_template % {'APPID': appid, - 'CNAME': appid.capitalize(), - 'CWPATH': abspath(join(dirname(__file__), '..'))}) - - -class DeleteInstanceCommand(Command): - """Delete an instance. Will remove instance's files and - unregister it. - """ - name = 'delete' - arguments = '' - min_args = max_args = 1 - options = () - - def run(self, args): - """run the command with its specific arguments""" - appid = args[0] - configs = [cwcfg.config_for(appid, configname) - for configname in cwcfg.possible_configurations(appid)] - if not configs: - raise ExecutionError('unable to guess configuration for %s' % appid) - for config in configs: - helper = self.config_helper(config, required=False) - if helper: - helper.cleanup() - # remove home - rm(config.apphome) - # remove instance data directory - try: - rm(config.appdatahome) - except OSError as ex: - import errno - if ex.errno != errno.ENOENT: - raise - confignames = ', '.join([config.name for config in configs]) - print('-> instance %s (%s) deleted.' % (appid, confignames)) - - -# instance commands ######################################################## - -class StartInstanceCommand(InstanceCommandFork): - """Start the given instances. If no instance is given, start them all. - - ... - identifiers of the instances to start. If no instance is - given, start them all. - """ - name = 'start' - actionverb = 'started' - options = ( - ("debug", - {'short': 'D', 'action' : 'store_true', - 'help': 'start server in debug mode.'}), - ("force", - {'short': 'f', 'action' : 'store_true', - 'default': False, - 'help': 'start the instance even if it seems to be already \ -running.'}), - ('profile', - {'short': 'P', 'type' : 'string', 'metavar': '', - 'default': None, - 'help': 'profile code and use the specified file to store stats', - }), - ('loglevel', - {'short': 'l', 'type' : 'choice', 'metavar': '', - 'default': None, 'choices': ('debug', 'info', 'warning', 'error'), - 'help': 'debug if -D is set, error otherwise', - }), - ('param', - {'short': 'p', 'type' : 'named', 'metavar' : 'key1:value1,key2:value2', - 'default': {}, - 'help': 'override configuration file option with .', - }), - ) - - def start_instance(self, appid): - """start the instance's server""" - try: - import twisted # noqa - except ImportError: - msg = ( - "Twisted is required by the 'start' command\n" - "Either install it, or use one of the alternative commands:\n" - "- '{ctl} wsgi {appid}'\n" - "- '{ctl} pyramid {appid}' (requires the pyramid cube)\n") - raise ExecutionError(msg.format(ctl='cubicweb-ctl', appid=appid)) - config = cwcfg.config_for(appid, debugmode=self['debug']) - # override config file values with cmdline options - config.cmdline_options = self.config.param - init_cmdline_log_threshold(config, self['loglevel']) - if self['profile']: - config.global_set_option('profile', self.config.profile) - helper = self.config_helper(config, cmdname='start') - pidf = config['pid-file'] - if exists(pidf) and not self['force']: - msg = "%s seems to be running. Remove %s by hand if necessary or use \ -the --force option." - raise ExecutionError(msg % (appid, pidf)) - if helper.start_server(config) == 1: - print('instance %s started' % appid) - - -def init_cmdline_log_threshold(config, loglevel): - if loglevel is not None: - config.global_set_option('log-threshold', loglevel.upper()) - config.init_log(config['log-threshold'], force=True) - - -class StopInstanceCommand(InstanceCommand): - """Stop the given instances. - - ... - identifiers of the instances to stop. If no instance is - given, stop them all. - """ - name = 'stop' - actionverb = 'stopped' - - def ordered_instances(self): - instances = super(StopInstanceCommand, self).ordered_instances() - instances.reverse() - return instances - - def stop_instance(self, appid): - """stop the instance's server""" - config = cwcfg.config_for(appid) - helper = self.config_helper(config, cmdname='stop') - helper.poststop() # do this anyway - pidf = config['pid-file'] - if not exists(pidf): - sys.stderr.write("%s doesn't exist.\n" % pidf) - return - import signal - pid = int(open(pidf).read().strip()) - try: - kill(pid, signal.SIGTERM) - except Exception: - sys.stderr.write("process %s seems already dead.\n" % pid) - else: - try: - wait_process_end(pid) - except ExecutionError as ex: - sys.stderr.write('%s\ntrying SIGKILL\n' % ex) - try: - kill(pid, signal.SIGKILL) - except Exception: - # probably dead now - pass - wait_process_end(pid) - try: - remove(pidf) - except OSError: - # already removed by twistd - pass - print('instance %s stopped' % appid) - - -class RestartInstanceCommand(StartInstanceCommand): - """Restart the given instances. - - ... - identifiers of the instances to restart. If no instance is - given, restart them all. - """ - name = 'restart' - actionverb = 'restarted' - - def run_args(self, args, askconfirm): - regdir = cwcfg.instances_dir() - if not isfile(join(regdir, 'startorder')) or len(args) <= 1: - # no specific startorder - super(RestartInstanceCommand, self).run_args(args, askconfirm) - return - print ('some specific start order is specified, will first stop all ' - 'instances then restart them.') - # get instances in startorder - for appid in args: - if askconfirm: - print('*'*72) - if not ASK.confirm('%s instance %r ?' % (self.name, appid)): - continue - StopInstanceCommand(self.logger).stop_instance(appid) - forkcmd = [w for w in sys.argv if not w in args] - forkcmd[1] = 'start' - forkcmd = ' '.join(forkcmd) - for appid in reversed(args): - status = system('%s %s' % (forkcmd, appid)) - if status: - sys.exit(status) - - def restart_instance(self, appid): - StopInstanceCommand(self.logger).stop_instance(appid) - self.start_instance(appid) - - -class ReloadConfigurationCommand(RestartInstanceCommand): - """Reload the given instances. This command is equivalent to a - restart for now. - - ... - identifiers of the instances to reload. If no instance is - given, reload them all. - """ - name = 'reload' - - def reload_instance(self, appid): - self.restart_instance(appid) - - -class StatusCommand(InstanceCommand): - """Display status information about the given instances. - - ... - identifiers of the instances to status. If no instance is - given, get status information about all registered instances. - """ - name = 'status' - options = () - - @staticmethod - def status_instance(appid): - """print running status information for an instance""" - status = 0 - for mode in cwcfg.possible_configurations(appid): - config = cwcfg.config_for(appid, mode) - print('[%s-%s]' % (appid, mode), end=' ') - try: - pidf = config['pid-file'] - except KeyError: - print('buggy instance, pid file not specified') - continue - if not exists(pidf): - print("doesn't seem to be running") - status = 1 - continue - pid = int(open(pidf).read().strip()) - # trick to guess whether or not the process is running - try: - getpgid(pid) - except OSError: - print("should be running with pid %s but the process can not be found" % pid) - status = 1 - continue - print("running with pid %s" % (pid)) - return status - -class UpgradeInstanceCommand(InstanceCommandFork): - """Upgrade an instance after cubicweb and/or component(s) upgrade. - - For repository update, you will be prompted for a login / password to use - to connect to the system database. For some upgrades, the given user - should have create or alter table permissions. - - ... - identifiers of the instances to upgrade. If no instance is - given, upgrade them all. - """ - name = 'upgrade' - actionverb = 'upgraded' - options = InstanceCommand.options + ( - ('force-cube-version', - {'short': 't', 'type' : 'named', 'metavar': 'cube1:X.Y.Z,cube2:X.Y.Z', - 'default': None, - 'help': 'force migration from the indicated version for the specified cube(s).'}), - - ('force-cubicweb-version', - {'short': 'e', 'type' : 'string', 'metavar': 'X.Y.Z', - 'default': None, - 'help': 'force migration from the indicated cubicweb version.'}), - - ('fs-only', - {'short': 's', 'action' : 'store_true', - 'default': False, - 'help': 'only upgrade files on the file system, not the database.'}), - - ('nostartstop', - {'short': 'n', 'action' : 'store_true', - 'default': False, - 'help': 'don\'t try to stop instance before migration and to restart it after.'}), - - ('verbosity', - {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', - 'default': 1, - 'help': "0: no confirmation, 1: only main commands confirmed, 2 ask \ -for everything."}), - - ('backup-db', - {'short': 'b', 'type' : 'yn', 'metavar': '', - 'default': None, - 'help': "Backup the instance database before upgrade.\n"\ - "If the option is ommitted, confirmation will be ask.", - }), - - ('ext-sources', - {'short': 'E', 'type' : 'csv', 'metavar': '', - 'default': None, - 'help': "For multisources instances, specify to which sources the \ -repository should connect to for upgrading. When unspecified or 'migration' is \ -given, appropriate sources for migration will be automatically selected \ -(recommended). If 'all' is given, will connect to all defined sources.", - }), - ) - - def upgrade_instance(self, appid): - print('\n' + underline_title('Upgrading the instance %s' % appid)) - from logilab.common.changelog import Version - config = cwcfg.config_for(appid) - instance_running = exists(config['pid-file']) - config.repairing = True # notice we're not starting the server - config.verbosity = self.config.verbosity - set_sources_mode = getattr(config, 'set_sources_mode', None) - if set_sources_mode is not None: - set_sources_mode(self.config.ext_sources or ('migration',)) - # get instance and installed versions for the server and the componants - mih = config.migration_handler() - repo = mih.repo - vcconf = repo.get_versions() - helper = self.config_helper(config, required=False) - if self.config.force_cube_version: - for cube, version in self.config.force_cube_version.items(): - vcconf[cube] = Version(version) - toupgrade = [] - for cube in config.cubes(): - installedversion = config.cube_version(cube) - try: - applversion = vcconf[cube] - except KeyError: - config.error('no version information for %s' % cube) - continue - if installedversion > applversion: - toupgrade.append( (cube, applversion, installedversion) ) - cubicwebversion = config.cubicweb_version() - if self.config.force_cubicweb_version: - applcubicwebversion = Version(self.config.force_cubicweb_version) - vcconf['cubicweb'] = applcubicwebversion - else: - applcubicwebversion = vcconf.get('cubicweb') - if cubicwebversion > applcubicwebversion: - toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion)) - # only stop once we're sure we have something to do - if instance_running and not (CWDEV or self.config.nostartstop): - StopInstanceCommand(self.logger).stop_instance(appid) - # run cubicweb/componants migration scripts - if self.config.fs_only or toupgrade: - for cube, fromversion, toversion in toupgrade: - print('-> migration needed from %s to %s for %s' % (fromversion, toversion, cube)) - with mih.cnx: - with mih.cnx.security_enabled(False, False): - mih.migrate(vcconf, reversed(toupgrade), self.config) - else: - print('-> no data migration needed for instance %s.' % appid) - # rewrite main configuration file - mih.rewrite_configuration() - mih.shutdown() - # handle i18n upgrade - if not self.i18nupgrade(config): - return - print() - if helper: - helper.postupgrade(repo) - print('-> instance migrated.') - if instance_running and not (CWDEV or self.config.nostartstop): - # restart instance through fork to get a proper environment, avoid - # uicfg pb (and probably gettext catalogs, to check...) - forkcmd = '%s start %s' % (sys.argv[0], appid) - status = system(forkcmd) - if status: - print('%s exited with status %s' % (forkcmd, status)) - print() - - def i18nupgrade(self, config): - # handle i18n upgrade: - # * install new languages - # * recompile catalogs - # XXX search available language in the first cube given - from cubicweb import i18n - templdir = cwcfg.cube_dir(config.cubes()[0]) - langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))] - errors = config.i18ncompile(langs) - if errors: - print('\n'.join(errors)) - if not ASK.confirm('Error while compiling message catalogs, ' - 'continue anyway?'): - print('-> migration not completed.') - return False - return True - - -class ListVersionsInstanceCommand(InstanceCommand): - """List versions used by an instance. - - ... - identifiers of the instances to list versions for. - """ - name = 'versions' - - def versions_instance(self, appid): - config = cwcfg.config_for(appid) - # should not raise error if db versions don't match fs versions - config.repairing = True - # no need to load all appobjects and schema - config.quick_start = True - if hasattr(config, 'set_sources_mode'): - config.set_sources_mode(('migration',)) - vcconf = config.repository().get_versions() - for key in sorted(vcconf): - print(key+': %s.%s.%s' % vcconf[key]) - -class ShellCommand(Command): - """Run an interactive migration shell on an instance. This is a python shell - with enhanced migration commands predefined in the namespace. An additional - argument may be given corresponding to a file containing commands to execute - in batch mode. - - By default it will connect to a local instance using an in memory - connection, unless a URL to a running instance is specified. - - Arguments after bare "--" string will not be processed by the shell command - You can use it to pass extra arguments to your script and expect for - them in '__args__' afterwards. - - - the identifier of the instance to connect. - """ - name = 'shell' - arguments = ' [batch command file(s)] [--