698 lines
37 KiB
Python
698 lines
37 KiB
Python
#flectra.loggers.handlers. -*- coding: utf-8 -*-
|
|
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
|
|
|
|
try:
|
|
import configparser as ConfigParser
|
|
except ImportError:
|
|
import ConfigParser
|
|
|
|
import errno
|
|
import logging
|
|
import optparse
|
|
import os
|
|
import sys
|
|
import shutil
|
|
import flectra
|
|
from contextlib import closing
|
|
from .. import release, conf, loglevels
|
|
from . import appdirs, pycompat
|
|
|
|
from passlib.context import CryptContext
|
|
crypt_context = CryptContext(schemes=['pbkdf2_sha512', 'plaintext'],
|
|
deprecated=['plaintext'])
|
|
|
|
class MyOption (optparse.Option, object):
|
|
""" optparse Option with two additional attributes.
|
|
|
|
The list of command line options (getopt.Option) is used to create the
|
|
list of the configuration file options. When reading the file, and then
|
|
reading the command line arguments, we don't want optparse.parse results
|
|
to override the configuration file values. But if we provide default
|
|
values to optparse, optparse will return them and we can't know if they
|
|
were really provided by the user or not. A solution is to not use
|
|
optparse's default attribute, but use a custom one (that will be copied
|
|
to create the default values of the configuration file).
|
|
|
|
"""
|
|
def __init__(self, *opts, **attrs):
|
|
self.my_default = attrs.pop('my_default', None)
|
|
super(MyOption, self).__init__(*opts, **attrs)
|
|
|
|
DEFAULT_LOG_HANDLER = ':INFO'
|
|
def _get_default_datadir():
|
|
home = os.path.expanduser('~')
|
|
if os.path.isdir(home):
|
|
func = appdirs.user_data_dir
|
|
else:
|
|
if sys.platform in ['win32', 'darwin']:
|
|
func = appdirs.site_data_dir
|
|
else:
|
|
func = lambda **kwarg: "/var/lib/%s" % kwarg['appname'].lower()
|
|
# No "version" kwarg as session and filestore paths are shared against series
|
|
return func(appname=release.product_name, appauthor=release.author)
|
|
|
|
def _deduplicate_loggers(loggers):
|
|
""" Avoid saving multiple logging levels for the same loggers to a save
|
|
file, that just takes space and the list can potentially grow unbounded
|
|
if for some odd reason people use :option`--save`` all the time.
|
|
"""
|
|
# dict(iterable) -> the last item of iterable for any given key wins,
|
|
# which is what we want and expect. Output order should not matter as
|
|
# there are no duplicates within the output sequence
|
|
return (
|
|
'{}:{}'.format(logger, level)
|
|
for logger, level in dict(it.split(':') for it in loggers).items()
|
|
)
|
|
|
|
class configmanager(object):
|
|
def __init__(self, fname=None):
|
|
"""Constructor.
|
|
|
|
:param fname: a shortcut allowing to instantiate :class:`configmanager`
|
|
from Python code without resorting to environment
|
|
variable
|
|
"""
|
|
# Options not exposed on the command line. Command line options will be added
|
|
# from optparse's parser.
|
|
self.options = {
|
|
'admin_passwd': 'admin',
|
|
'csv_internal_sep': ',',
|
|
'publisher_warranty_url': 'https://services.flectrahq.com/publisher-warranty/',
|
|
'reportgz': False,
|
|
'root_path': None,
|
|
}
|
|
|
|
# Not exposed in the configuration file.
|
|
self.blacklist_for_save = set([
|
|
'publisher_warranty_url', 'load_language', 'root_path',
|
|
'init', 'save', 'config', 'update', 'stop_after_init', 'dev_mode', 'shell_interface'
|
|
])
|
|
|
|
# dictionary mapping option destination (keys in self.options) to MyOptions.
|
|
self.casts = {}
|
|
|
|
self.misc = {}
|
|
self.config_file = fname
|
|
|
|
self._LOGLEVELS = dict([
|
|
(getattr(loglevels, 'LOG_%s' % x), getattr(logging, x))
|
|
for x in ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET')
|
|
])
|
|
|
|
version = "%s %s" % (release.description, release.version)
|
|
self.parser = parser = optparse.OptionParser(version=version, option_class=MyOption)
|
|
|
|
# Server startup config
|
|
group = optparse.OptionGroup(parser, "Common options")
|
|
group.add_option("-c", "--config", dest="config", help="specify alternate config file")
|
|
group.add_option("-s", "--save", action="store_true", dest="save", default=False,
|
|
help="save configuration to ~/.flectrarc (or to ~/.flectra_serverrc if it exists)")
|
|
group.add_option("-i", "--init", dest="init", help="install one or more modules (comma-separated list, use \"all\" for all modules), requires -d")
|
|
group.add_option("-u", "--update", dest="update",
|
|
help="update one or more modules (comma-separated list, use \"all\" for all modules). Requires -d.")
|
|
group.add_option("--without-demo", dest="without_demo",
|
|
help="disable loading demo data for modules to be installed (comma-separated, use \"all\" for all modules). Requires -d and -i. Default is %default",
|
|
my_default=False)
|
|
group.add_option("-P", "--import-partial", dest="import_partial", my_default='',
|
|
help="Use this for big data importation, if it crashes you will be able to continue at the current state. Provide a filename to store intermediate importation states.")
|
|
group.add_option("--pidfile", dest="pidfile", help="file where the server pid will be stored")
|
|
group.add_option("--addons-path", dest="addons_path",
|
|
help="specify additional addons paths (separated by commas).",
|
|
action="callback", callback=self._check_addons_path, nargs=1, type="string")
|
|
group.add_option("--load", dest="server_wide_modules", help="Comma-separated list of server-wide modules.", my_default='web')
|
|
|
|
group.add_option("-D", "--data-dir", dest="data_dir", my_default=_get_default_datadir(),
|
|
help="Directory where to store Flectra data")
|
|
parser.add_option_group(group)
|
|
|
|
# HTTP
|
|
group = optparse.OptionGroup(parser, "HTTP Service Configuration")
|
|
group.add_option("--http-interface", dest="http_interface", my_default='',
|
|
help="Listen interface address for HTTP services. "
|
|
"Keep empty to listen on all interfaces (0.0.0.0)")
|
|
group.add_option("-p", "--http-port", dest="http_port", my_default=7073,
|
|
help="Listen port for the main HTTP service", type="int", metavar="PORT")
|
|
group.add_option("--longpolling-port", dest="longpolling_port", my_default=7072,
|
|
help="Listen port for the longpolling HTTP service", type="int", metavar="PORT")
|
|
group.add_option("--no-http", dest="http_enable", action="store_false", my_default=True,
|
|
help="Disable the HTTP and Longpolling services entirely")
|
|
group.add_option("--proxy-mode", dest="proxy_mode", action="store_true", my_default=False,
|
|
help="Activate reverse proxy WSGI wrappers (headers rewriting) "
|
|
"Only enable this when running behind a trusted web proxy!")
|
|
# HTTP: hidden backwards-compatibility for "*xmlrpc*" options
|
|
hidden = optparse.SUPPRESS_HELP
|
|
group.add_option("--xmlrpc-interface", dest="http_interface", help=hidden)
|
|
group.add_option("--xmlrpc-port", dest="http_port", type="int", help=hidden)
|
|
group.add_option("--no-xmlrpc", dest="http_enable", action="store_false", help=hidden)
|
|
|
|
parser.add_option_group(group)
|
|
|
|
# WEB
|
|
group = optparse.OptionGroup(parser, "Web interface Configuration")
|
|
group.add_option("--db-filter", dest="dbfilter", my_default='', metavar="REGEXP",
|
|
help="Regular expressions for filtering available databases for Web UI. "
|
|
"The expression can use %d (domain) and %h (host) placeholders.")
|
|
parser.add_option_group(group)
|
|
|
|
# Testing Group
|
|
group = optparse.OptionGroup(parser, "Testing Configuration")
|
|
group.add_option("--test-file", dest="test_file", my_default=False,
|
|
help="Launch a python or YML test file.")
|
|
group.add_option("--test-report-directory", dest="test_report_directory", my_default=False,
|
|
help="If set, will save sample of all reports in this directory.")
|
|
group.add_option("--test-enable", action="store_true", dest="test_enable",
|
|
my_default=False, help="Enable YAML and unit tests.")
|
|
group.add_option("--test-commit", action="store_true", dest="test_commit",
|
|
my_default=False, help="Commit database changes performed by YAML or XML tests.")
|
|
parser.add_option_group(group)
|
|
|
|
# Logging Group
|
|
group = optparse.OptionGroup(parser, "Logging Configuration")
|
|
group.add_option("--logfile", dest="logfile", help="file where the server log will be stored")
|
|
group.add_option("--logrotate", dest="logrotate", action="store_true", my_default=False, help="enable logfile rotation")
|
|
group.add_option("--syslog", action="store_true", dest="syslog", my_default=False, help="Send the log to the syslog server")
|
|
group.add_option('--log-handler', action="append", default=[], my_default=DEFAULT_LOG_HANDLER, metavar="PREFIX:LEVEL", help='setup a handler at LEVEL for a given PREFIX. An empty PREFIX indicates the root logger. This option can be repeated. Example: "flectra.orm:DEBUG" or "werkzeug:CRITICAL" (default: ":INFO")')
|
|
group.add_option('--log-request', action="append_const", dest="log_handler", const="flectra.http.rpc.request:DEBUG", help='shortcut for --log-handler=flectra.http.rpc.request:DEBUG')
|
|
group.add_option('--log-response', action="append_const", dest="log_handler", const="flectra.http.rpc.response:DEBUG", help='shortcut for --log-handler=flectra.http.rpc.response:DEBUG')
|
|
group.add_option('--log-web', action="append_const", dest="log_handler", const="flectra.http:DEBUG", help='shortcut for --log-handler=flectra.http:DEBUG')
|
|
group.add_option('--log-sql', action="append_const", dest="log_handler", const="flectra.sql_db:DEBUG", help='shortcut for --log-handler=flectra.sql_db:DEBUG')
|
|
group.add_option('--log-db', dest='log_db', help="Logging database", my_default=False)
|
|
group.add_option('--log-db-level', dest='log_db_level', my_default='warning', help="Logging database level")
|
|
# For backward-compatibility, map the old log levels to something
|
|
# quite close.
|
|
levels = [
|
|
'info', 'debug_rpc', 'warn', 'test', 'critical',
|
|
'debug_sql', 'error', 'debug', 'debug_rpc_answer', 'notset'
|
|
]
|
|
group.add_option('--log-level', dest='log_level', type='choice',
|
|
choices=levels, my_default='info',
|
|
help='specify the level of the logging. Accepted values: %s.' % (levels,))
|
|
|
|
parser.add_option_group(group)
|
|
|
|
# SMTP Group
|
|
group = optparse.OptionGroup(parser, "SMTP Configuration")
|
|
group.add_option('--email-from', dest='email_from', my_default=False,
|
|
help='specify the SMTP email address for sending email')
|
|
group.add_option('--smtp', dest='smtp_server', my_default='localhost',
|
|
help='specify the SMTP server for sending email')
|
|
group.add_option('--smtp-port', dest='smtp_port', my_default=25,
|
|
help='specify the SMTP port', type="int")
|
|
group.add_option('--smtp-ssl', dest='smtp_ssl', action='store_true', my_default=False,
|
|
help='if passed, SMTP connections will be encrypted with SSL (STARTTLS)')
|
|
group.add_option('--smtp-user', dest='smtp_user', my_default=False,
|
|
help='specify the SMTP username for sending email')
|
|
group.add_option('--smtp-password', dest='smtp_password', my_default=False,
|
|
help='specify the SMTP password for sending email')
|
|
parser.add_option_group(group)
|
|
|
|
group = optparse.OptionGroup(parser, "Database related options")
|
|
group.add_option("-d", "--database", dest="db_name", my_default=False,
|
|
help="specify the database name")
|
|
group.add_option("-r", "--db_user", dest="db_user", my_default=False,
|
|
help="specify the database user name")
|
|
group.add_option("-w", "--db_password", dest="db_password", my_default=False,
|
|
help="specify the database password")
|
|
group.add_option("--pg_path", dest="pg_path", help="specify the pg executable path")
|
|
group.add_option("--db_host", dest="db_host", my_default=False,
|
|
help="specify the database host")
|
|
group.add_option("--db_port", dest="db_port", my_default=False,
|
|
help="specify the database port", type="int")
|
|
group.add_option("--db_sslmode", dest="db_sslmode", type="choice", my_default='prefer',
|
|
choices=['disable', 'allow', 'prefer', 'require', 'verify-ca', 'verify-full'],
|
|
help="specify the database ssl connection mode (see PostgreSQL documentation)")
|
|
group.add_option("--db_maxconn", dest="db_maxconn", type='int', my_default=64,
|
|
help="specify the the maximum number of physical connections to posgresql")
|
|
group.add_option("--db-template", dest="db_template", my_default="template1",
|
|
help="specify a custom database template to create a new database")
|
|
parser.add_option_group(group)
|
|
|
|
group = optparse.OptionGroup(parser, "Internationalisation options. ",
|
|
"Use these options to translate Flectra to another language. "
|
|
"See i18n section of the user manual. Option '-d' is mandatory. "
|
|
"Option '-l' is mandatory in case of importation"
|
|
)
|
|
group.add_option('--load-language', dest="load_language",
|
|
help="specifies the languages for the translations you want to be loaded")
|
|
group.add_option('-l', "--language", dest="language",
|
|
help="specify the language of the translation file. Use it with --i18n-export or --i18n-import")
|
|
group.add_option("--i18n-export", dest="translate_out",
|
|
help="export all sentences to be translated to a CSV file, a PO file or a TGZ archive and exit")
|
|
group.add_option("--i18n-import", dest="translate_in",
|
|
help="import a CSV or a PO file with translations and exit. The '-l' option is required.")
|
|
group.add_option("--i18n-overwrite", dest="overwrite_existing_translations", action="store_true", my_default=False,
|
|
help="overwrites existing translation terms on updating a module or importing a CSV or a PO file.")
|
|
group.add_option("--modules", dest="translate_modules",
|
|
help="specify modules to export. Use in combination with --i18n-export")
|
|
parser.add_option_group(group)
|
|
|
|
security = optparse.OptionGroup(parser, 'Security-related options')
|
|
security.add_option('--no-database-list', action="store_false", dest='list_db', my_default=True,
|
|
help="Disable the ability to obtain or view the list of databases. "
|
|
"Also disable access to the database manager and selector, "
|
|
"so be sure to set a proper --database parameter first")
|
|
parser.add_option_group(security)
|
|
|
|
# Advanced options
|
|
group = optparse.OptionGroup(parser, "Advanced options")
|
|
group.add_option('--dev', dest='dev_mode', type="string",
|
|
help="Enable developer mode. Param: List of options separated by comma. "
|
|
"Options : all, [pudb|wdb|ipdb|pdb], reload, qweb, werkzeug, xml")
|
|
group.add_option('--shell-interface', dest='shell_interface', type="string",
|
|
help="Specify a preferred REPL to use in shell mode. Supported REPLs are: "
|
|
"[ipython|ptpython|bpython|python]")
|
|
group.add_option("--stop-after-init", action="store_true", dest="stop_after_init", my_default=False,
|
|
help="stop the server after its initialization")
|
|
group.add_option("--osv-memory-count-limit", dest="osv_memory_count_limit", my_default=False,
|
|
help="Force a limit on the maximum number of records kept in the virtual "
|
|
"osv_memory tables. The default is False, which means no count-based limit.",
|
|
type="int")
|
|
group.add_option("--osv-memory-age-limit", dest="osv_memory_age_limit", my_default=1.0,
|
|
help="Force a limit on the maximum age of records kept in the virtual "
|
|
"osv_memory tables. This is a decimal value expressed in hours, "
|
|
"and the default is 1 hour.",
|
|
type="float")
|
|
group.add_option("--max-cron-threads", dest="max_cron_threads", my_default=2,
|
|
help="Maximum number of threads processing concurrently cron jobs (default 2).",
|
|
type="int")
|
|
group.add_option("--unaccent", dest="unaccent", my_default=False, action="store_true",
|
|
help="Use the unaccent function provided by the database when available.")
|
|
group.add_option("--geoip-db", dest="geoip_database", my_default='/usr/share/GeoIP/GeoLiteCity.dat',
|
|
help="Absolute path to the GeoIP database file.")
|
|
group.add_option("--app-store", dest="app_store",
|
|
help="specify the option to enable app store. (default : install) "
|
|
"Options : [install|download|disable]", my_default='install')
|
|
parser.add_option_group(group)
|
|
|
|
if os.name == 'posix':
|
|
group = optparse.OptionGroup(parser, "Multiprocessing options")
|
|
# TODO sensible default for the three following limits.
|
|
group.add_option("--workers", dest="workers", my_default=0,
|
|
help="Specify the number of workers, 0 disable prefork mode.",
|
|
type="int")
|
|
group.add_option("--limit-memory-soft", dest="limit_memory_soft", my_default=2048 * 1024 * 1024,
|
|
help="Maximum allowed virtual memory per worker, when reached the worker be "
|
|
"reset after the current request (default 2048MiB).",
|
|
type="int")
|
|
group.add_option("--limit-memory-hard", dest="limit_memory_hard", my_default=2560 * 1024 * 1024,
|
|
help="Maximum allowed virtual memory per worker, when reached, any memory "
|
|
"allocation will fail (default 2560MiB).",
|
|
type="int")
|
|
group.add_option("--limit-time-cpu", dest="limit_time_cpu", my_default=60,
|
|
help="Maximum allowed CPU time per request (default 60).",
|
|
type="int")
|
|
group.add_option("--limit-time-real", dest="limit_time_real", my_default=120,
|
|
help="Maximum allowed Real time per request (default 120).",
|
|
type="int")
|
|
group.add_option("--limit-time-real-cron", dest="limit_time_real_cron", my_default=-1,
|
|
help="Maximum allowed Real time per cron job. (default: --limit-time-real). "
|
|
"Set to 0 for no limit. ",
|
|
type="int")
|
|
group.add_option("--limit-request", dest="limit_request", my_default=8192,
|
|
help="Maximum number of request to be processed per worker (default 8192).",
|
|
type="int")
|
|
parser.add_option_group(group)
|
|
|
|
# Copy all optparse options (i.e. MyOption) into self.options.
|
|
for group in parser.option_groups:
|
|
for option in group.option_list:
|
|
if option.dest not in self.options:
|
|
self.options[option.dest] = option.my_default
|
|
self.casts[option.dest] = option
|
|
|
|
# generate default config
|
|
self._parse_config()
|
|
|
|
def parse_config(self, args=None):
|
|
""" Parse the configuration file (if any) and the command-line
|
|
arguments.
|
|
|
|
This method initializes flectra.tools.config and flectra.conf (the
|
|
former should be removed in the furture) with library-wide
|
|
configuration values.
|
|
|
|
This method must be called before proper usage of this library can be
|
|
made.
|
|
|
|
Typical usage of this method:
|
|
|
|
flectra.tools.config.parse_config(sys.argv[1:])
|
|
"""
|
|
self._parse_config(args)
|
|
flectra.netsvc.init_logger()
|
|
flectra.modules.module.initialize_sys_path()
|
|
|
|
def _parse_config(self, args=None):
|
|
if args is None:
|
|
args = []
|
|
opt, args = self.parser.parse_args(args)
|
|
|
|
def die(cond, msg):
|
|
if cond:
|
|
self.parser.error(msg)
|
|
|
|
# Ensures no illegitimate argument is silently discarded (avoids insidious "hyphen to dash" problem)
|
|
die(args, "unrecognized parameters: '%s'" % " ".join(args))
|
|
|
|
die(bool(opt.syslog) and bool(opt.logfile),
|
|
"the syslog and logfile options are exclusive")
|
|
|
|
die(opt.translate_in and (not opt.language or not opt.db_name),
|
|
"the i18n-import option cannot be used without the language (-l) and the database (-d) options")
|
|
|
|
die(opt.overwrite_existing_translations and not (opt.translate_in or opt.update),
|
|
"the i18n-overwrite option cannot be used without the i18n-import option or without the update option")
|
|
|
|
die(opt.translate_out and (not opt.db_name),
|
|
"the i18n-export option cannot be used without the database (-d) option")
|
|
|
|
# Check if the config file exists (-c used, but not -s)
|
|
die(not opt.save and opt.config and not os.access(opt.config, os.R_OK),
|
|
"The config file '%s' selected with -c/--config doesn't exist or is not readable, "\
|
|
"use -s/--save if you want to generate it"% opt.config)
|
|
|
|
# place/search the config file on Win32 near the server installation
|
|
# (../etc from the server)
|
|
# if the server is run by an unprivileged user, he has to specify location of a config file where he has the rights to write,
|
|
# else he won't be able to save the configurations, or even to start the server...
|
|
# TODO use appdirs
|
|
if os.name == 'nt':
|
|
rcfilepath = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'flectra.conf')
|
|
else:
|
|
rcfilepath = os.path.expanduser('~/.flectrarc')
|
|
old_rcfilepath = os.path.expanduser('~/.flectra_serverrc')
|
|
|
|
die(os.path.isfile(rcfilepath) and os.path.isfile(old_rcfilepath),
|
|
"Found '.flectrarc' and '.flectra_serverrc' in your path. Please keep only one of "\
|
|
"them, preferrably '.flectrarc'.")
|
|
|
|
if not os.path.isfile(rcfilepath) and os.path.isfile(old_rcfilepath):
|
|
rcfilepath = old_rcfilepath
|
|
|
|
self.rcfile = os.path.abspath(
|
|
self.config_file or opt.config or os.environ.get('FLECTRA_RC') or os.environ.get('OPENERP_SERVER') or rcfilepath)
|
|
self.load()
|
|
|
|
# Verify that we want to log or not, if not the output will go to stdout
|
|
if self.options['logfile'] in ('None', 'False'):
|
|
self.options['logfile'] = False
|
|
# the same for the pidfile
|
|
if self.options['pidfile'] in ('None', 'False'):
|
|
self.options['pidfile'] = False
|
|
# and the server_wide_modules
|
|
if self.options['server_wide_modules'] in ('', 'None', 'False'):
|
|
self.options['server_wide_modules'] = 'web'
|
|
|
|
# if defined dont take the configfile value even if the defined value is None
|
|
keys = ['http_interface', 'http_port', 'longpolling_port', 'http_enable',
|
|
'db_name', 'db_user', 'db_password', 'db_host', 'db_sslmode',
|
|
'db_port', 'db_template', 'logfile', 'pidfile', 'smtp_port',
|
|
'email_from', 'smtp_server', 'smtp_user', 'smtp_password',
|
|
'db_maxconn', 'import_partial', 'addons_path',
|
|
'syslog', 'without_demo',
|
|
'dbfilter', 'log_level', 'log_db',
|
|
'log_db_level', 'geoip_database', 'dev_mode', 'shell_interface',
|
|
'app_store'
|
|
]
|
|
|
|
for arg in keys:
|
|
# Copy the command-line argument (except the special case for log_handler, due to
|
|
# action=append requiring a real default, so we cannot use the my_default workaround)
|
|
if getattr(opt, arg):
|
|
self.options[arg] = getattr(opt, arg)
|
|
# ... or keep, but cast, the config file value.
|
|
elif isinstance(self.options[arg], pycompat.string_types) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
|
|
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
|
|
|
|
if isinstance(self.options['log_handler'], pycompat.string_types):
|
|
self.options['log_handler'] = self.options['log_handler'].split(',')
|
|
self.options['log_handler'].extend(opt.log_handler)
|
|
|
|
# if defined but None take the configfile value
|
|
keys = [
|
|
'language', 'translate_out', 'translate_in', 'overwrite_existing_translations',
|
|
'dev_mode', 'shell_interface', 'smtp_ssl', 'load_language',
|
|
'stop_after_init', 'logrotate', 'without_demo', 'http_enable', 'syslog',
|
|
'list_db', 'proxy_mode',
|
|
'test_file', 'test_enable', 'test_commit', 'test_report_directory',
|
|
'osv_memory_count_limit', 'osv_memory_age_limit', 'max_cron_threads', 'unaccent',
|
|
'data_dir',
|
|
'server_wide_modules',
|
|
]
|
|
|
|
posix_keys = [
|
|
'workers',
|
|
'limit_memory_hard', 'limit_memory_soft',
|
|
'limit_time_cpu', 'limit_time_real', 'limit_request', 'limit_time_real_cron'
|
|
]
|
|
|
|
if os.name == 'posix':
|
|
keys += posix_keys
|
|
else:
|
|
self.options.update(dict.fromkeys(posix_keys, None))
|
|
|
|
# Copy the command-line arguments...
|
|
for arg in keys:
|
|
if getattr(opt, arg) is not None:
|
|
self.options[arg] = getattr(opt, arg)
|
|
# ... or keep, but cast, the config file value.
|
|
elif isinstance(self.options[arg], pycompat.string_types) and self.casts[arg].type in optparse.Option.TYPE_CHECKER:
|
|
self.options[arg] = optparse.Option.TYPE_CHECKER[self.casts[arg].type](self.casts[arg], arg, self.options[arg])
|
|
|
|
self.options['root_path'] = os.path.abspath(os.path.expanduser(os.path.expandvars(os.path.join(os.path.dirname(__file__), '..'))))
|
|
if not self.options['addons_path'] or self.options['addons_path']=='None':
|
|
default_addons = []
|
|
base_addons = os.path.join(self.options['root_path'], 'addons')
|
|
if os.path.exists(base_addons):
|
|
default_addons.append(base_addons)
|
|
main_addons = os.path.abspath(os.path.join(self.options['root_path'], '../addons'))
|
|
if os.path.exists(main_addons):
|
|
default_addons.append(main_addons)
|
|
self.options['addons_path'] = ','.join(default_addons)
|
|
else:
|
|
self.options['addons_path'] = ",".join(
|
|
os.path.abspath(os.path.expanduser(os.path.expandvars(x.strip())))
|
|
for x in self.options['addons_path'].split(','))
|
|
|
|
self.options['data_dir'] = os.path.abspath(os.path.expanduser(os.path.expandvars(self.options['data_dir'].strip())))
|
|
|
|
self.options['init'] = opt.init and dict.fromkeys(opt.init.split(','), 1) or {}
|
|
self.options['demo'] = (dict(self.options['init'])
|
|
if not self.options['without_demo'] else {})
|
|
self.options['update'] = opt.update and dict.fromkeys(opt.update.split(','), 1) or {}
|
|
self.options['translate_modules'] = opt.translate_modules and [m.strip() for m in opt.translate_modules.split(',')] or ['all']
|
|
self.options['translate_modules'].sort()
|
|
|
|
dev_split = opt.dev_mode and [s.strip() for s in opt.dev_mode.split(',')] or []
|
|
self.options['dev_mode'] = 'all' in dev_split and dev_split + ['pdb', 'reload', 'qweb', 'werkzeug', 'xml'] or dev_split
|
|
|
|
if opt.pg_path:
|
|
self.options['pg_path'] = opt.pg_path
|
|
|
|
if self.options.get('language', False):
|
|
if len(self.options['language']) > 5:
|
|
raise Exception('ERROR: The Lang name must take max 5 chars, Eg: -lfr_BE')
|
|
|
|
if opt.save:
|
|
self.save()
|
|
|
|
conf.addons_paths = self.options['addons_path'].split(',')
|
|
|
|
conf.server_wide_modules = [
|
|
m.strip() for m in self.options['server_wide_modules'].split(',') if m.strip()
|
|
]
|
|
|
|
def _is_addons_path(self, path):
|
|
from flectra.modules.module import MANIFEST_NAMES
|
|
for f in os.listdir(path):
|
|
modpath = os.path.join(path, f)
|
|
if os.path.isdir(modpath):
|
|
def hasfile(filename):
|
|
return os.path.isfile(os.path.join(modpath, filename))
|
|
if hasfile('__init__.py') and any(hasfile(mname) for mname in MANIFEST_NAMES):
|
|
return True
|
|
return False
|
|
|
|
def _check_addons_path(self, option, opt, value, parser):
|
|
ad_paths = []
|
|
for path in value.split(','):
|
|
path = path.strip()
|
|
res = os.path.abspath(os.path.expanduser(path))
|
|
if not os.path.isdir(res):
|
|
raise optparse.OptionValueError("option %s: no such directory: %r" % (opt, path))
|
|
if not self._is_addons_path(res):
|
|
raise optparse.OptionValueError("option %s: The addons-path %r does not seem to a be a valid Addons Directory!" % (opt, path))
|
|
ad_paths.append(res)
|
|
|
|
setattr(parser.values, option.dest, ",".join(ad_paths))
|
|
|
|
def load(self):
|
|
outdated_options_map = {
|
|
'xmlrpc_port': 'http_port',
|
|
'xmlrpc_interface': 'http_interface',
|
|
'xmlrpc': 'http_enable',
|
|
}
|
|
p = ConfigParser.RawConfigParser()
|
|
try:
|
|
p.read([self.rcfile])
|
|
for (name,value) in p.items('options'):
|
|
name = outdated_options_map.get(name, name)
|
|
if value=='True' or value=='true':
|
|
value = True
|
|
if value=='False' or value=='false':
|
|
value = False
|
|
self.options[name] = value
|
|
#parse the other sections, as well
|
|
for sec in p.sections():
|
|
if sec == 'options':
|
|
continue
|
|
self.misc.setdefault(sec, {})
|
|
for (name, value) in p.items(sec):
|
|
if value=='True' or value=='true':
|
|
value = True
|
|
if value=='False' or value=='false':
|
|
value = False
|
|
self.misc[sec][name] = value
|
|
except IOError:
|
|
pass
|
|
except ConfigParser.NoSectionError:
|
|
pass
|
|
|
|
def save(self):
|
|
p = ConfigParser.RawConfigParser()
|
|
loglevelnames = dict(pycompat.izip(self._LOGLEVELS.values(), self._LOGLEVELS))
|
|
p.add_section('options')
|
|
for opt in sorted(self.options):
|
|
if opt in ('version', 'language', 'translate_out', 'translate_in', 'overwrite_existing_translations', 'init', 'update'):
|
|
continue
|
|
if opt in self.blacklist_for_save:
|
|
continue
|
|
if opt in ('log_level',):
|
|
p.set('options', opt, loglevelnames.get(self.options[opt], self.options[opt]))
|
|
elif opt == 'log_handler':
|
|
p.set('options', opt, ','.join(_deduplicate_loggers(self.options[opt])))
|
|
else:
|
|
p.set('options', opt, self.options[opt])
|
|
|
|
for sec in sorted(self.misc):
|
|
p.add_section(sec)
|
|
for opt in sorted(self.misc[sec]):
|
|
p.set(sec,opt,self.misc[sec][opt])
|
|
|
|
# try to create the directories and write the file
|
|
try:
|
|
rc_exists = os.path.exists(self.rcfile)
|
|
if not rc_exists and not os.path.exists(os.path.dirname(self.rcfile)):
|
|
os.makedirs(os.path.dirname(self.rcfile))
|
|
try:
|
|
p.write(open(self.rcfile, 'w'))
|
|
if not rc_exists:
|
|
os.chmod(self.rcfile, 0o600)
|
|
except IOError:
|
|
sys.stderr.write("ERROR: couldn't write the config file\n")
|
|
|
|
except OSError:
|
|
# what to do if impossible?
|
|
sys.stderr.write("ERROR: couldn't create the config directory\n")
|
|
|
|
def get(self, key, default=None):
|
|
return self.options.get(key, default)
|
|
|
|
def pop(self, key, default=None):
|
|
return self.options.pop(key, default)
|
|
|
|
def get_misc(self, sect, key, default=None):
|
|
return self.misc.get(sect,{}).get(key, default)
|
|
|
|
def __setitem__(self, key, value):
|
|
self.options[key] = value
|
|
if key in self.options and isinstance(self.options[key], pycompat.string_types) and \
|
|
key in self.casts and self.casts[key].type in optparse.Option.TYPE_CHECKER:
|
|
self.options[key] = optparse.Option.TYPE_CHECKER[self.casts[key].type](self.casts[key], key, self.options[key])
|
|
|
|
def __getitem__(self, key):
|
|
return self.options[key]
|
|
|
|
def copytree(self, src, dst, symlinks=False, ignore=None):
|
|
for item in os.listdir(src):
|
|
s = os.path.join(src, item)
|
|
d = os.path.join(dst, item)
|
|
if os.path.isdir(s):
|
|
shutil.copytree(s, d, symlinks, ignore)
|
|
else:
|
|
shutil.copy2(s, d)
|
|
|
|
@property
|
|
def addons_data_dir(self):
|
|
add_dir = os.path.join(self['data_dir'], 'addons')
|
|
d = os.path.join(add_dir, release.series)
|
|
if not os.path.exists(d):
|
|
try:
|
|
# bootstrap parent dir +rwx
|
|
if not os.path.exists(add_dir):
|
|
os.makedirs(add_dir, 0o700)
|
|
# try to make +rx placeholder dir, will need manual +w to activate it
|
|
os.makedirs(d, 0o700)
|
|
except OSError:
|
|
logging.getLogger(__name__).debug('Failed to create addons data dir %s', d)
|
|
try:
|
|
try:
|
|
from flectra.http import request, root
|
|
if request.session.db and not os.listdir(os.path.join(d)):
|
|
from flectra.sql_db import db_connect
|
|
with closing(db_connect(request.session.db).cursor()) as cr:
|
|
if flectra.tools.table_exists(cr, 'ir_module_module'):
|
|
cr.execute("SELECT latest_version FROM ir_module_module WHERE name=%s", ('base',))
|
|
base_version = cr.fetchone()
|
|
if base_version and base_version[0]:
|
|
tmp = base_version[0].split('.')[:2]
|
|
last_version = '.'.join(str(v) for v in tmp)
|
|
s = os.path.join(add_dir, last_version)
|
|
if float(last_version) < float(release.series) and os.listdir(os.path.join(s)):
|
|
self.copytree(s, d)
|
|
root.load_addons()
|
|
except:
|
|
pass
|
|
|
|
if self.get('app_store') == 'install':
|
|
if not os.access(d, os.W_OK):
|
|
os.chmod(d, 0o700)
|
|
else:
|
|
if os.access(d, os.W_OK):
|
|
os.chmod(d, 0o500)
|
|
except OSError:
|
|
logging.getLogger(__name__).debug("No such file or directory: %s", d)
|
|
return d
|
|
|
|
@property
|
|
def session_dir(self):
|
|
d = os.path.join(self['data_dir'], 'sessions')
|
|
try:
|
|
os.makedirs(d, 0o700)
|
|
except OSError as e:
|
|
if e.errno != errno.EEXIST:
|
|
raise
|
|
assert os.access(d, os.W_OK), \
|
|
"%s: directory is not writable" % d
|
|
return d
|
|
|
|
def filestore(self, dbname):
|
|
return os.path.join(self['data_dir'], 'filestore', dbname)
|
|
|
|
def set_admin_password(self, new_password):
|
|
self.options['admin_passwd'] = crypt_context.encrypt(new_password)
|
|
|
|
def verify_admin_password(self, password):
|
|
"""Verifies the super-admin password, possibly updating the stored hash if needed"""
|
|
stored_hash = self.options['admin_passwd']
|
|
if not stored_hash:
|
|
# empty password/hash => authentication forbidden
|
|
return False
|
|
result, updated_hash = crypt_context.verify_and_update(password, stored_hash)
|
|
if result:
|
|
if updated_hash:
|
|
self.options['admin_passwd'] = updated_hash
|
|
return True
|
|
|
|
config = configmanager()
|