server/serverconfig.py
changeset 11057 0b59724cb3f2
parent 11052 058bb3dc685f
child 11058 23eb30449fe5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
     1 # copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """server.serverconfig definition"""
       
    19 from __future__ import print_function
       
    20 
       
    21 __docformat__ = "restructuredtext en"
       
    22 
       
    23 import sys
       
    24 from os.path import join, exists
       
    25 
       
    26 from six.moves import StringIO
       
    27 
       
    28 import logilab.common.configuration as lgconfig
       
    29 from logilab.common.decorators import cached
       
    30 
       
    31 from cubicweb.toolsutils import read_config, restrict_perms_to_user
       
    32 from cubicweb.cwconfig import CONFIGURATIONS, CubicWebConfiguration
       
    33 from cubicweb.server import SOURCE_TYPES
       
    34 
       
    35 
       
    36 USER_OPTIONS =  (
       
    37     ('login', {'type' : 'string',
       
    38                'default': 'admin',
       
    39                'help': "cubicweb manager account's login "
       
    40                '(this user will be created)',
       
    41                'level': 0,
       
    42                }),
       
    43     ('password', {'type' : 'password',
       
    44                   'default': lgconfig.REQUIRED,
       
    45                   'help': "cubicweb manager account's password",
       
    46                   'level': 0,
       
    47                   }),
       
    48     )
       
    49 
       
    50 class SourceConfiguration(lgconfig.Configuration):
       
    51     def __init__(self, appconfig, options):
       
    52         self.appconfig = appconfig # has to be done before super call
       
    53         super(SourceConfiguration, self).__init__(options=options)
       
    54 
       
    55     # make Method('default_instance_id') usable in db option defs (in native.py)
       
    56     def default_instance_id(self):
       
    57         return self.appconfig.appid
       
    58 
       
    59     def input_option(self, option, optdict, inputlevel):
       
    60         try:
       
    61             dbdriver = self['db-driver']
       
    62         except lgconfig.OptionError:
       
    63             pass
       
    64         else:
       
    65             if dbdriver == 'sqlite':
       
    66                 if option in ('db-user', 'db-password'):
       
    67                     return
       
    68                 if option == 'db-name':
       
    69                     optdict = optdict.copy()
       
    70                     optdict['help'] = 'path to the sqlite database'
       
    71                     optdict['default'] = join(self.appconfig.appdatahome,
       
    72                                               self.appconfig.appid + '.sqlite')
       
    73         super(SourceConfiguration, self).input_option(option, optdict, inputlevel)
       
    74 
       
    75 
       
    76 
       
    77 def ask_source_config(appconfig, type, inputlevel=0):
       
    78     options = SOURCE_TYPES[type].options
       
    79     sconfig = SourceConfiguration(appconfig, options=options)
       
    80     sconfig.input_config(inputlevel=inputlevel)
       
    81     return sconfig
       
    82 
       
    83 def generate_source_config(sconfig, encoding=sys.stdin.encoding):
       
    84     """serialize a repository source configuration as text"""
       
    85     stream = StringIO()
       
    86     optsbysect = list(sconfig.options_by_section())
       
    87     assert len(optsbysect) == 1, (
       
    88         'all options for a source should be in the same group, got %s'
       
    89         % [x[0] for x in optsbysect])
       
    90     lgconfig.ini_format(stream, optsbysect[0][1], encoding)
       
    91     return stream.getvalue()
       
    92 
       
    93 
       
    94 class ServerConfiguration(CubicWebConfiguration):
       
    95     """standalone RQL server"""
       
    96     name = 'repository'
       
    97 
       
    98     cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects', 'hooks'])
       
    99     cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['sobjects', 'hooks'])
       
   100 
       
   101     options = lgconfig.merge_options((
       
   102         # ctl configuration
       
   103         ('host',
       
   104          {'type' : 'string',
       
   105           'default': None,
       
   106           'help': 'host name if not correctly detectable through gethostname',
       
   107           'group': 'main', 'level': 1,
       
   108           }),
       
   109         ('pid-file',
       
   110          {'type' : 'string',
       
   111           'default': lgconfig.Method('default_pid_file'),
       
   112           'help': 'repository\'s pid file',
       
   113           'group': 'main', 'level': 2,
       
   114           }),
       
   115         ('uid',
       
   116          {'type' : 'string',
       
   117           'default': None,
       
   118           'help': 'if this option is set, use the specified user to start \
       
   119 the repository rather than the user running the command',
       
   120           'group': 'main', 'level': (CubicWebConfiguration.mode == 'installed') and 0 or 1,
       
   121           }),
       
   122         ('cleanup-session-time',
       
   123          {'type' : 'time',
       
   124           'default': '24h',
       
   125           'help': 'duration of inactivity after which a session '
       
   126           'will be closed, to limit memory consumption (avoid sessions that '
       
   127           'never expire and cause memory leak when http-session-time is 0, or '
       
   128           'because of bad client that never closes their connection). '
       
   129           'So notice that even if http-session-time is 0 and the user don\'t '
       
   130           'close his browser, he will have to reauthenticate after this time '
       
   131           'of inactivity. Default to 24h.',
       
   132           'group': 'main', 'level': 3,
       
   133           }),
       
   134         ('connections-pool-size',
       
   135          {'type' : 'int',
       
   136           'default': 4,
       
   137           'help': 'size of the connections pool. Each source supporting multiple \
       
   138 connections will have this number of opened connections.',
       
   139           'group': 'main', 'level': 3,
       
   140           }),
       
   141         ('rql-cache-size',
       
   142          {'type' : 'int',
       
   143           'default': 3000,
       
   144           'help': 'size of the parsed rql cache size.',
       
   145           'group': 'main', 'level': 3,
       
   146           }),
       
   147         ('undo-enabled',
       
   148          {'type' : 'yn', 'default': False,
       
   149           'help': 'enable undo support',
       
   150           'group': 'main', 'level': 3,
       
   151           }),
       
   152         ('keep-transaction-lifetime',
       
   153          {'type' : 'int', 'default': 7,
       
   154           'help': 'number of days during which transaction records should be \
       
   155 kept (hence undoable).',
       
   156           'group': 'main', 'level': 3,
       
   157           }),
       
   158         ('multi-sources-etypes',
       
   159          {'type' : 'csv', 'default': (),
       
   160           'help': 'defines which entity types from this repository are used \
       
   161 by some other instances. You should set this properly for these instances to \
       
   162 detect updates / deletions.',
       
   163           'group': 'main', 'level': 3,
       
   164           }),
       
   165 
       
   166         ('delay-full-text-indexation',
       
   167          {'type' : 'yn', 'default': False,
       
   168           'help': 'When full text indexation of entity has a too important cost'
       
   169           ' to be done when entity are added/modified by users, activate this '
       
   170           'option and setup a job using cubicweb-ctl db-rebuild-fti on your '
       
   171           'system (using cron for instance).',
       
   172           'group': 'main', 'level': 3,
       
   173           }),
       
   174 
       
   175         # email configuration
       
   176         ('default-recipients-mode',
       
   177          {'type' : 'choice',
       
   178           'choices' : ('default-dest-addrs', 'users', 'none'),
       
   179           'default': 'default-dest-addrs',
       
   180           'help': 'when a notification should be sent with no specific rules \
       
   181 to find recipients, recipients will be found according to this mode. Available \
       
   182 modes are "default-dest-addrs" (emails specified in the configuration \
       
   183 variable with the same name), "users" (every users which has activated \
       
   184 account with an email set), "none" (no notification).',
       
   185           'group': 'email', 'level': 2,
       
   186           }),
       
   187         ('default-dest-addrs',
       
   188          {'type' : 'csv',
       
   189           'default': (),
       
   190           'help': 'comma separated list of email addresses that will be used \
       
   191 as default recipient when an email is sent and the notification has no \
       
   192 specific recipient rules.',
       
   193           'group': 'email', 'level': 2,
       
   194           }),
       
   195         ('supervising-addrs',
       
   196          {'type' : 'csv',
       
   197           'default': (),
       
   198           'help': 'comma separated list of email addresses that will be \
       
   199 notified of every changes.',
       
   200           'group': 'email', 'level': 2,
       
   201           }),
       
   202          ('zmq-address-sub',
       
   203           {'type' : 'csv',
       
   204            'default' : (),
       
   205            'help': ('List of ZMQ addresses to subscribe to (requires pyzmq) '
       
   206                     '(of the form `tcp://<ipaddr>:<port>`)'),
       
   207            'group': 'zmq', 'level': 1,
       
   208            }),
       
   209          ('zmq-address-pub',
       
   210           {'type' : 'string',
       
   211            'default' : None,
       
   212            'help': ('ZMQ address to use for publishing (requires pyzmq) '
       
   213                     '(of the form `tcp://<ipaddr>:<port>`)'),
       
   214            'group': 'zmq', 'level': 1,
       
   215            }),
       
   216         ) + CubicWebConfiguration.options)
       
   217 
       
   218     # should we init the connections pool (eg connect to sources). This is
       
   219     # usually necessary...
       
   220     init_cnxset_pool = True
       
   221 
       
   222     # read the schema from the database
       
   223     read_instance_schema = True
       
   224     # set this to true to get a minimal repository, for instance to get cubes
       
   225     # information on commands such as i18ninstance, db-restore, etc...
       
   226     quick_start = False
       
   227     # check user's state at login time
       
   228     consider_user_state = True
       
   229 
       
   230     # should some hooks be deactivated during [pre|post]create script execution
       
   231     free_wheel = False
       
   232 
       
   233     # list of enables sources when sources restriction is necessary
       
   234     # (eg repository initialization at least)
       
   235     enabled_sources = None
       
   236 
       
   237     def bootstrap_cubes(self):
       
   238         from logilab.common.textutils import splitstrip
       
   239         with open(join(self.apphome, 'bootstrap_cubes')) as f:
       
   240             for line in f:
       
   241                 line = line.strip()
       
   242                 if not line or line.startswith('#'):
       
   243                     continue
       
   244                 self.init_cubes(self.expand_cubes(splitstrip(line)))
       
   245                 break
       
   246             else:
       
   247                 # no cubes
       
   248                 self.init_cubes(())
       
   249 
       
   250     def write_bootstrap_cubes_file(self, cubes):
       
   251         stream = open(join(self.apphome, 'bootstrap_cubes'), 'w')
       
   252         stream.write('# this is a generated file only used for bootstraping\n')
       
   253         stream.write('# you should not have to edit this\n')
       
   254         stream.write('%s\n' % ','.join(cubes))
       
   255         stream.close()
       
   256 
       
   257     def sources_file(self):
       
   258         return join(self.apphome, 'sources')
       
   259 
       
   260     # this method has to be cached since when the server is running using a
       
   261     # restricted user, this user usually don't have access to the sources
       
   262     # configuration file (#16102)
       
   263     @cached
       
   264     def read_sources_file(self):
       
   265         """return a dictionary of values found in the sources file"""
       
   266         return read_config(self.sources_file(), raise_if_unreadable=True)
       
   267 
       
   268     @property
       
   269     def system_source_config(self):
       
   270         return self.read_sources_file()['system']
       
   271 
       
   272     @property
       
   273     def default_admin_config(self):
       
   274         return self.read_sources_file()['admin']
       
   275 
       
   276     def source_enabled(self, source):
       
   277         if self.sources_mode is not None:
       
   278             if 'migration' in self.sources_mode:
       
   279                 assert len(self.sources_mode) == 1
       
   280                 if source.connect_for_migration:
       
   281                     return True
       
   282                 print('not connecting to source', source.uri, 'during migration')
       
   283                 return False
       
   284             if 'all' in self.sources_mode:
       
   285                 assert len(self.sources_mode) == 1
       
   286                 return True
       
   287             return source.uri in self.sources_mode
       
   288         if self.quick_start:
       
   289             return source.uri == 'system'
       
   290         return (not source.disabled and (
       
   291             not self.enabled_sources or source.uri in self.enabled_sources))
       
   292 
       
   293     def write_sources_file(self, sourcescfg):
       
   294         """serialize repository'sources configuration into a INI like file"""
       
   295         sourcesfile = self.sources_file()
       
   296         if exists(sourcesfile):
       
   297             import shutil
       
   298             shutil.copy(sourcesfile, sourcesfile + '.bak')
       
   299         stream = open(sourcesfile, 'w')
       
   300         for section in ('admin', 'system'):
       
   301             sconfig = sourcescfg[section]
       
   302             if isinstance(sconfig, dict):
       
   303                 # get a Configuration object
       
   304                 assert section == 'system', '%r is not system' % section
       
   305                 _sconfig = SourceConfiguration(
       
   306                     self, options=SOURCE_TYPES['native'].options)
       
   307                 for attr, val in sconfig.items():
       
   308                     try:
       
   309                         _sconfig.set_option(attr, val)
       
   310                     except lgconfig.OptionError:
       
   311                         # skip adapter, may be present on pre 3.10 instances
       
   312                         if attr != 'adapter':
       
   313                             self.error('skip unknown option %s in sources file' % attr)
       
   314                 sconfig = _sconfig
       
   315             stream.write('[%s]\n%s\n' % (section, generate_source_config(sconfig)))
       
   316         restrict_perms_to_user(sourcesfile)
       
   317 
       
   318     def load_schema(self, expand_cubes=False, **kwargs):
       
   319         from cubicweb.schema import CubicWebSchemaLoader
       
   320         if expand_cubes:
       
   321             # in case some new dependencies have been introduced, we have to
       
   322             # reinitialize cubes so the full filesystem schema is read
       
   323             origcubes = self.cubes()
       
   324             self._cubes = None
       
   325             self.init_cubes(self.expand_cubes(origcubes))
       
   326         schema = CubicWebSchemaLoader().load(self, **kwargs)
       
   327         if expand_cubes:
       
   328             # restore original value
       
   329             self._cubes = origcubes
       
   330         return schema
       
   331 
       
   332     def load_bootstrap_schema(self):
       
   333         from cubicweb.schema import BootstrapSchemaLoader
       
   334         schema = BootstrapSchemaLoader().load(self)
       
   335         schema.name = 'bootstrap'
       
   336         return schema
       
   337 
       
   338     sources_mode = None
       
   339     def set_sources_mode(self, sources):
       
   340         self.sources_mode = sources
       
   341 
       
   342     def migration_handler(self, schema=None, interactive=True,
       
   343                           cnx=None, repo=None, connect=True, verbosity=None):
       
   344         """return a migration handler instance"""
       
   345         from cubicweb.server.migractions import ServerMigrationHelper
       
   346         if verbosity is None:
       
   347             verbosity = getattr(self, 'verbosity', 0)
       
   348         return ServerMigrationHelper(self, schema, interactive=interactive,
       
   349                                      cnx=cnx, repo=repo, connect=connect,
       
   350                                      verbosity=verbosity)