cwctl.py
brancholdstable
changeset 4985 02b52bf9f5f8
parent 4798 d5bd706e9005
child 5021 58e89f3dfbae
child 5042 cc062bb09abb
equal deleted inserted replaced
4563:c25da7573ebd 4985:02b52bf9f5f8
     1 """%%prog %s [options] %s
     1 """the cubicweb-ctl tool, based on logilab.common.clcommands to
     2 
     2 provide a pluggable commands system.
     3 CubicWeb main instances controller.
     3 
       
     4 
       
     5 :organization: Logilab
       
     6 :copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
       
     7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     4 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
     5 %s"""
     9 """
     6 
    10 __docformat__ = "restructuredtext en"
       
    11 
       
    12 # *ctl module should limit the number of import to be imported as quickly as
       
    13 # possible (for cubicweb-ctl reactivity, necessary for instance for usable bash
       
    14 # completion). So import locally in command helpers.
     7 import sys
    15 import sys
     8 from os import remove, listdir, system, pathsep
    16 from os import remove, listdir, system, pathsep
     9 try:
    17 try:
    10     from os import kill, getpgid
    18     from os import kill, getpgid
    11 except ImportError:
    19 except ImportError:
    12     def kill(*args): pass
    20     def kill(*args):
    13     def getpgid(): pass
    21         """win32 kill implementation"""
       
    22     def getpgid():
       
    23         """win32 getpgid implementation"""
    14 
    24 
    15 from os.path import exists, join, isfile, isdir, dirname, abspath
    25 from os.path import exists, join, isfile, isdir, dirname, abspath
    16 
    26 
    17 from logilab.common.clcommands import register_commands, pop_arg
    27 from logilab.common.clcommands import register_commands, pop_arg
    18 from logilab.common.shellutils import ASK
    28 from logilab.common.shellutils import ASK
    19 
    29 
    20 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title
    30 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
    21 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
    31 from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
    22 from cubicweb.toolsutils import Command, main_run,  rm, create_dir
    32 from cubicweb.toolsutils import Command, main_run, rm, create_dir, underline_title
    23 
    33 
    24 def wait_process_end(pid, maxtry=10, waittime=1):
    34 def wait_process_end(pid, maxtry=10, waittime=1):
    25     """wait for a process to actually die"""
    35     """wait for a process to actually die"""
    26     import signal
    36     import signal
    27     from time import sleep
    37     from time import sleep
   156                 self.run_arg(appid)
   166                 self.run_arg(appid)
   157 
   167 
   158 
   168 
   159 # base commands ###############################################################
   169 # base commands ###############################################################
   160 
   170 
       
   171 def version_strictly_lower(a, b):
       
   172     from logilab.common.changelog import Version
       
   173     if a:
       
   174         a = Version(a)
       
   175     if b:
       
   176         b = Version(b)
       
   177     return a < b
       
   178 
       
   179 def max_version(a, b):
       
   180     from logilab.common.changelog import Version
       
   181     return str(max(Version(a), Version(b)))
       
   182 
       
   183 class ConfigurationProblem(object):
       
   184     """Each cube has its own list of dependencies on other cubes/versions.
       
   185 
       
   186     The ConfigurationProblem is used to record the loaded cubes, then to detect
       
   187     inconsistencies in their dependencies.
       
   188 
       
   189     See configuration management on wikipedia for litterature.
       
   190     """
       
   191 
       
   192     def __init__(self):
       
   193         self.cubes = {}
       
   194 
       
   195     def add_cube(self, name, info):
       
   196         self.cubes[name] = info
       
   197 
       
   198     def solve(self):
       
   199         self.warnings = []
       
   200         self.errors = []
       
   201         self.read_constraints()
       
   202         for cube, versions in sorted(self.constraints.items()):
       
   203             oper, version = None, None
       
   204             # simplify constraints
       
   205             if versions:
       
   206                 for constraint in versions:
       
   207                     op, ver = constraint
       
   208                     if oper is None:
       
   209                         oper = op
       
   210                         version = ver
       
   211                     elif op == '>=' and oper == '>=':
       
   212                         version = max_version(ver, version)
       
   213                     else:
       
   214                         print 'unable to handle this case', oper, version, op, ver
       
   215             # "solve" constraint satisfaction problem
       
   216             if cube not in self.cubes:
       
   217                 self.errors.append( ('add', cube, version) )
       
   218             elif versions:
       
   219                 lower_strict = version_strictly_lower(self.cubes[cube].version, version)
       
   220                 if oper in ('>=','='):
       
   221                     if lower_strict:
       
   222                         self.errors.append( ('update', cube, version) )
       
   223                 else:
       
   224                     print 'unknown operator', oper
       
   225 
       
   226     def read_constraints(self):
       
   227         self.constraints = {}
       
   228         self.reverse_constraints = {}
       
   229         for cube, info in self.cubes.items():
       
   230             if hasattr(info,'__depends_cubes__'):
       
   231                 use = info.__depends_cubes__
       
   232                 if not isinstance(use, dict):
       
   233                     use = dict((key, None) for key in use)
       
   234                     self.warnings.append('cube %s should define __depends_cubes__ as a dict not a list')
       
   235             else:
       
   236                 self.warnings.append('cube %s should define __depends_cubes__' % cube)
       
   237                 use = dict((key, None) for key in info.__use__)
       
   238             for name, constraint in use.items():
       
   239                 self.constraints.setdefault(name,set())
       
   240                 if constraint:
       
   241                     try:
       
   242                         oper, version = constraint.split()
       
   243                         self.constraints[name].add( (oper, version) )
       
   244                     except:
       
   245                         self.warnings.append('cube %s depends on %s but constraint badly formatted: %s'
       
   246                                              % (cube, name, constraint))
       
   247                 self.reverse_constraints.setdefault(name, set()).add(cube)
       
   248 
   161 class ListCommand(Command):
   249 class ListCommand(Command):
   162     """List configurations, cubes and instances.
   250     """List configurations, cubes and instances.
   163 
   251 
   164     list available configurations, installed cubes, and registered instances
   252     list available configurations, installed cubes, and registered instances
   165     """
   253     """
   183                 line = line.strip()
   271                 line = line.strip()
   184                 if not line:
   272                 if not line:
   185                     continue
   273                     continue
   186                 print '   ', line
   274                 print '   ', line
   187         print
   275         print
       
   276         cfgpb = ConfigurationProblem()
   188         try:
   277         try:
   189             cubesdir = pathsep.join(cwcfg.cubes_search_path())
   278             cubesdir = pathsep.join(cwcfg.cubes_search_path())
   190             namesize = max(len(x) for x in cwcfg.available_cubes())
   279             namesize = max(len(x) for x in cwcfg.available_cubes())
   191         except ConfigurationError, ex:
   280         except ConfigurationError, ex:
   192             print 'No cubes available:', ex
   281             print 'No cubes available:', ex
   198                 if cube in ('CVS', '.svn', 'shared', '.hg'):
   287                 if cube in ('CVS', '.svn', 'shared', '.hg'):
   199                     continue
   288                     continue
   200                 try:
   289                 try:
   201                     tinfo = cwcfg.cube_pkginfo(cube)
   290                     tinfo = cwcfg.cube_pkginfo(cube)
   202                     tversion = tinfo.version
   291                     tversion = tinfo.version
       
   292                     cfgpb.add_cube(cube, tinfo)
   203                 except ConfigurationError:
   293                 except ConfigurationError:
   204                     tinfo = None
   294                     tinfo = None
   205                     tversion = '[missing cube information]'
   295                     tversion = '[missing cube information]'
   206                 print '* %s %s' % (cube.ljust(namesize), tversion)
   296                 print '* %s %s' % (cube.ljust(namesize), tversion)
   207                 if self.config.verbose:
   297                 if self.config.verbose:
   233                     print '    (BROKEN instance, %s)' % exc
   323                     print '    (BROKEN instance, %s)' % exc
   234                     continue
   324                     continue
   235         else:
   325         else:
   236             print 'No instance available in %s' % regdir
   326             print 'No instance available in %s' % regdir
   237         print
   327         print
   238 
   328         # configuration management problem solving
       
   329         cfgpb.solve()
       
   330         if cfgpb.warnings:
       
   331             print 'Warnings:\n', '\n'.join('* '+txt for txt in cfgpb.warnings)
       
   332         if cfgpb.errors:
       
   333             print 'Errors:'
       
   334             for op, cube, version in cfgpb.errors:
       
   335                 if op == 'add':
       
   336                     print '* cube', cube,
       
   337                     if version:
       
   338                         print ' version', version,
       
   339                     print 'is not installed, but required by %s' % ' '.join(cfgpb.reverse_constraints[cube])
       
   340                 else:
       
   341                     print '* cube %s version %s is installed, but version %s is required by (%s)' % (
       
   342                         cube, cfgpb.cubes[cube].version, version, ', '.join(cfgpb.reverse_constraints[cube]))
   239 
   343 
   240 class CreateInstanceCommand(Command):
   344 class CreateInstanceCommand(Command):
   241     """Create an instance from a cube. This is an unified
   345     """Create an instance from a cube. This is an unified
   242     command which can handle web / server / all-in-one installation
   346     command which can handle web / server / all-in-one installation
   243     according to available parts of the software library and of the
   347     according to available parts of the software library and of the
   295             print ', '.join(cwcfg.available_cubes())
   399             print ', '.join(cwcfg.available_cubes())
   296             return
   400             return
   297         # create the registry directory for this instance
   401         # create the registry directory for this instance
   298         print '\n'+underline_title('Creating the instance %s' % appid)
   402         print '\n'+underline_title('Creating the instance %s' % appid)
   299         create_dir(config.apphome)
   403         create_dir(config.apphome)
   300         # load site_cubicweb from the cubes dir (if any)
       
   301         config.load_site_cubicweb()
       
   302         # cubicweb-ctl configuration
   404         # cubicweb-ctl configuration
   303         print '\n'+underline_title('Configuring the instance (%s.conf)' % configname)
   405         print '\n'+underline_title('Configuring the instance (%s.conf)' % configname)
   304         config.input_config('main', self.config.config_level)
   406         config.input_config('main', self.config.config_level)
   305         # configuration'specific stuff
   407         # configuration'specific stuff
   306         print
   408         print
   307         helper.bootstrap(cubes, self.config.config_level)
   409         helper.bootstrap(cubes, self.config.config_level)
       
   410         # input for cubes specific options
       
   411         for section in set(sect.lower() for sect, opt, optdict in config.all_options()
       
   412                            if optdict.get('inputlevel') <= self.config.config_level):
       
   413             if section not in ('main', 'email', 'pyro'):
       
   414                 print '\n' + underline_title('%s options' % section)
       
   415                 config.input_config(section, self.config.config_level)
   308         # write down configuration
   416         # write down configuration
   309         config.save()
   417         config.save()
   310         self._handle_win32(config, appid)
   418         self._handle_win32(config, appid)
   311         print '-> generated %s' % config.main_config_file()
   419         print '-> generated %s' % config.main_config_file()
   312         # handle i18n files structure
   420         # handle i18n files structure
   313         # in the first cube given
   421         # in the first cube given
   314         print '-> preparing i18n catalogs'
   422         print '-> preparing i18n catalogs'
   315         from cubicweb.common import i18n
   423         from cubicweb import i18n
   316         langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
   424         langs = [lang for lang, _ in i18n.available_catalogs(join(templdirs[0], 'i18n'))]
   317         errors = config.i18ncompile(langs)
   425         errors = config.i18ncompile(langs)
   318         if errors:
   426         if errors:
   319             print '\n'.join(errors)
   427             print '\n'.join(errors)
   320             if not ASK.confirm('error while compiling message catalogs, '
   428             if not ASK.confirm('error while compiling message catalogs, '
   688         mih.rewrite_configuration()
   796         mih.rewrite_configuration()
   689         # handle i18n upgrade:
   797         # handle i18n upgrade:
   690         # * install new languages
   798         # * install new languages
   691         # * recompile catalogs
   799         # * recompile catalogs
   692         # in the first componant given
   800         # in the first componant given
   693         from cubicweb.common import i18n
   801         from cubicweb import i18n
   694         templdir = cwcfg.cube_dir(config.cubes()[0])
   802         templdir = cwcfg.cube_dir(config.cubes()[0])
   695         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
   803         langs = [lang for lang, _ in i18n.available_catalogs(join(templdir, 'i18n'))]
   696         errors = config.i18ncompile(langs)
   804         errors = config.i18ncompile(langs)
   697         if errors:
   805         if errors:
   698             print '\n'.join(errors)
   806             print '\n'.join(errors)
   881 
   989 
   882 
   990 
   883 def run(args):
   991 def run(args):
   884     """command line tool"""
   992     """command line tool"""
   885     cwcfg.load_cwctl_plugins()
   993     cwcfg.load_cwctl_plugins()
   886     main_run(args, __doc__)
   994     main_run(args, """%%prog %s [options] %s
       
   995 
       
   996 The CubicWeb swiss-knife.
       
   997 
       
   998 %s"""
       
   999 )
   887 
  1000 
   888 if __name__ == '__main__':
  1001 if __name__ == '__main__':
   889     run(sys.argv[1:])
  1002     run(sys.argv[1:])