commit b82c4c03d09c8b7e46f2b6aeb716385a14c373ff
Author: Francois POIROTTE <francois.poirotte@c-s.fr>
Date:   Fri Nov 13 16:22:03 2015 +0100

    [#1510] Chargement plus générique de l'application
    
    Rend plus générique le chargement des IHMs :
    - Le dossier de surcharge (public/) est pris à partir de l'emplacement
      du fichier settings.ini plutôt que hard-codé.
    - Idem pour le fichier app_cfg.py.
    
    Ces modifications permettent d'héberger plusieurs instances de VigiMap
    conjointement sur la même machine (par exemple, pour PreludeMap).
    
    Change-Id: Iaf43087160023a7eda95c3d366a336ba17e0b93f
    Refs: #1510.
    Reviewed-on: https://vigilo-dev.si.c-s.fr/review/1926
    Tested-by: Build system <qa@vigilo-dev.si.c-s.fr>
    Reviewed-by: Francois POIROTTE <francois.poirotte@c-s.fr>

diff --git a/MANIFEST.in b/MANIFEST.in
index e97a460..fac3edc 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -13,3 +13,4 @@ graft vigiboard/public
 include *.ini
 include jsl.cfg
 include babeljs.py
+include app_cfg.py
diff --git a/Makefile b/Makefile
index 2283cf9..7a16141 100644
--- a/Makefile
+++ b/Makefile
@@ -49,10 +49,8 @@ install_data: $(SUBST_FILES)
 	mkdir -p $(DESTDIR)$(LOCALSTATEDIR)/log/vigilo/$(NAME)
 	[ `id -u` -ne 0 ] || chown $(HTTPD_USER): $(DESTDIR)$(LOCALSTATEDIR)/log/vigilo/$(NAME)
 	install -m 644 -p -D deployment/logrotate.conf $(DESTDIR)/etc/logrotate.d/$(PKGNAME)
-	# Déplacement du app_cfg.py
-	mv $(DESTDIR)`grep '$(NAME)/config/app_cfg.py$$' INSTALLED_FILES` $(DESTDIR)$(SYSCONFDIR)/vigilo/$(NAME)/
-	ln -s $(SYSCONFDIR)/vigilo/$(NAME)/app_cfg.py $(DESTDIR)`grep '$(NAME)/config/app_cfg.py$$' INSTALLED_FILES`
-	echo $(SYSCONFDIR)/vigilo/$(NAME)/app_cfg.py >> INSTALLED_FILES
+	# Installation du app_cfg.py
+	install -m 644 -p app_cfg.py $(DESTDIR)$(SYSCONFDIR)/vigilo/$(NAME)/
 	# Cache
 	mkdir -p $(DESTDIR)$(LOCALSTATEDIR)/cache/vigilo/sessions
 	chmod 750 $(DESTDIR)$(LOCALSTATEDIR)/cache/vigilo/sessions
diff --git a/app_cfg.py b/app_cfg.py
new file mode 100644
index 0000000..005bdd2
--- /dev/null
+++ b/app_cfg.py
@@ -0,0 +1,158 @@
+# -*- coding: utf-8 -*-
+# vim: set fileencoding=utf-8 sw=4 ts=4 et :
+################################################################################
+#
+# Copyright (C) 2007-2015 CS-SI
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+################################################################################
+
+"""
+Global configuration file for TG2-specific settings in vigiboard.
+
+This file complements development/deployment.ini.
+
+Please note that **all the argument values are strings**. If you want to
+convert them into boolean, for example, you should use the
+:func:`paste.deploy.converters.asbool` function, as in::
+
+    from paste.deploy.converters import asbool
+    setting = asbool(global_conf.get('the_setting'))
+
+"""
+
+from vigilo.turbogears import VigiloAppConfig
+
+import vigiboard
+from vigiboard.lib import app_globals, helpers # pylint: disable-msg=W0611
+# W0611: Unused import: imports nécessaires pour le fonctionnement
+
+
+import logging
+LOGGER = logging.getLogger(__name__)
+
+class VigiboardConfig(VigiloAppConfig):
+    def setup_sqlalchemy(self):
+        super(VigiboardConfig, self).setup_sqlalchemy()
+
+        # On est obligés d'attendre que la base de données
+        # soit configurée pour charger les plugins.
+        from pkg_resources import working_set
+        from vigiboard.controllers.plugins import VigiboardRequestPlugin
+        from tg import config
+
+        plugins = []
+        for plugin_name in config['vigiboard_plugins']:
+            try:
+                ep = working_set.iter_entry_points(
+                        "vigiboard.columns", plugin_name).next()
+            except StopIteration:
+                pass
+
+            if ep.name in dict(plugins):
+                continue
+
+            try:
+                plugin_class = ep.load(require=True)
+                if issubclass(plugin_class, VigiboardRequestPlugin):
+                    plugins.append((unicode(ep.name), plugin_class()))
+            except Exception: # pylint: disable-msg=W0703
+                # W0703: Catch "Exception"
+                LOGGER.exception(u'Unable to import plugin %s', plugin_name)
+
+        config['columns_plugins'] = plugins
+
+base_config = VigiboardConfig('VigiBoard')
+base_config.package = vigiboard
+base_config.mimetype_lookup = {
+    '.csv': 'text/csv',
+}
+
+##################################
+# Settings specific to Vigiboard #
+##################################
+
+# Configuration des liens
+# Les elements suivants peuvent etre utilises dans la chaine de formatage :
+# - %(idcorrevent)d : identifiant de l'aggregat (alerte correlee)
+# - %(host)s : le nom de l'hote concerne par l'alerte
+# - %(service)s : le nom du service concerne par l'alerte
+# - %(message) : le message transmis par Nagios dans l'alerte
+#
+# Permet de satisfaire l'exigence VIGILO_EXIG_VIGILO_BAC_0130.
+base_config['vigiboard_links.eventdetails'] = (
+    {
+        'label': u'Détail de l\'hôte dans Nagios',
+        'uri': '/nagios/%(host)s/cgi-bin/status.cgi?host=%(host)s',
+    },
+    {
+        'label': u'Détail de la métrologie',
+        'uri': 'http://vigilo.example.com/vigilo/vigigraph/rpc/fullHostPage?host=%(host)s',
+    },
+    {
+        'label': u'Détail de la sécurité',
+        'uri': 'http://security.example.com/?host=%(host)s',
+    },
+    {
+        'label': 'Inventaire',
+        'uri': 'http://cmdb.example.com/?host=%(host)s',
+    },
+    {
+        'label': 'Documentation',
+        'uri': 'http://doc.example.com/?q=%(message)s',
+    },
+)
+
+# URL des tickets, possibilités:
+# - %(idcorrevent)d
+# - %(host)s
+# - %(service)s
+# - %(tt)s
+base_config['vigiboard_links.tt'] = 'http://bugs.example.com/?ticket=%(tt)s'
+
+# Plugins to use
+base_config['vigiboard_plugins'] = (
+#    'id',
+    'details',
+#    'state',
+    'groups',
+    'date',
+    'priority',
+    'occurrences',
+    'hostname',
+    'servicename',
+    'output',
+#    'masked_events',
+    'hls',
+    'status',
+#    'test',
+    'map',
+)
+
+base_config['csv_columns'] = (
+    'id',
+    'state',
+    'initial_state',
+    'peak_state',
+    'date',
+    'duration',
+    'priority',
+    'occurrences',
+    'hostname',
+    'servicename',
+    'output',
+    'ack',
+    'trouble_ticket_id',
+    'trouble_ticket_link',
+)
diff --git a/vigiboard/config/app_cfg.py b/vigiboard/config/app_cfg.py
deleted file mode 100644
index 005bdd2..0000000
--- a/vigiboard/config/app_cfg.py
+++ /dev/null
@@ -1,158 +0,0 @@
-# -*- coding: utf-8 -*-
-# vim: set fileencoding=utf-8 sw=4 ts=4 et :
-################################################################################
-#
-# Copyright (C) 2007-2015 CS-SI
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-################################################################################
-
-"""
-Global configuration file for TG2-specific settings in vigiboard.
-
-This file complements development/deployment.ini.
-
-Please note that **all the argument values are strings**. If you want to
-convert them into boolean, for example, you should use the
-:func:`paste.deploy.converters.asbool` function, as in::
-
-    from paste.deploy.converters import asbool
-    setting = asbool(global_conf.get('the_setting'))
-
-"""
-
-from vigilo.turbogears import VigiloAppConfig
-
-import vigiboard
-from vigiboard.lib import app_globals, helpers # pylint: disable-msg=W0611
-# W0611: Unused import: imports nécessaires pour le fonctionnement
-
-
-import logging
-LOGGER = logging.getLogger(__name__)
-
-class VigiboardConfig(VigiloAppConfig):
-    def setup_sqlalchemy(self):
-        super(VigiboardConfig, self).setup_sqlalchemy()
-
-        # On est obligés d'attendre que la base de données
-        # soit configurée pour charger les plugins.
-        from pkg_resources import working_set
-        from vigiboard.controllers.plugins import VigiboardRequestPlugin
-        from tg import config
-
-        plugins = []
-        for plugin_name in config['vigiboard_plugins']:
-            try:
-                ep = working_set.iter_entry_points(
-                        "vigiboard.columns", plugin_name).next()
-            except StopIteration:
-                pass
-
-            if ep.name in dict(plugins):
-                continue
-
-            try:
-                plugin_class = ep.load(require=True)
-                if issubclass(plugin_class, VigiboardRequestPlugin):
-                    plugins.append((unicode(ep.name), plugin_class()))
-            except Exception: # pylint: disable-msg=W0703
-                # W0703: Catch "Exception"
-                LOGGER.exception(u'Unable to import plugin %s', plugin_name)
-
-        config['columns_plugins'] = plugins
-
-base_config = VigiboardConfig('VigiBoard')
-base_config.package = vigiboard
-base_config.mimetype_lookup = {
-    '.csv': 'text/csv',
-}
-
-##################################
-# Settings specific to Vigiboard #
-##################################
-
-# Configuration des liens
-# Les elements suivants peuvent etre utilises dans la chaine de formatage :
-# - %(idcorrevent)d : identifiant de l'aggregat (alerte correlee)
-# - %(host)s : le nom de l'hote concerne par l'alerte
-# - %(service)s : le nom du service concerne par l'alerte
-# - %(message) : le message transmis par Nagios dans l'alerte
-#
-# Permet de satisfaire l'exigence VIGILO_EXIG_VIGILO_BAC_0130.
-base_config['vigiboard_links.eventdetails'] = (
-    {
-        'label': u'Détail de l\'hôte dans Nagios',
-        'uri': '/nagios/%(host)s/cgi-bin/status.cgi?host=%(host)s',
-    },
-    {
-        'label': u'Détail de la métrologie',
-        'uri': 'http://vigilo.example.com/vigilo/vigigraph/rpc/fullHostPage?host=%(host)s',
-    },
-    {
-        'label': u'Détail de la sécurité',
-        'uri': 'http://security.example.com/?host=%(host)s',
-    },
-    {
-        'label': 'Inventaire',
-        'uri': 'http://cmdb.example.com/?host=%(host)s',
-    },
-    {
-        'label': 'Documentation',
-        'uri': 'http://doc.example.com/?q=%(message)s',
-    },
-)
-
-# URL des tickets, possibilités:
-# - %(idcorrevent)d
-# - %(host)s
-# - %(service)s
-# - %(tt)s
-base_config['vigiboard_links.tt'] = 'http://bugs.example.com/?ticket=%(tt)s'
-
-# Plugins to use
-base_config['vigiboard_plugins'] = (
-#    'id',
-    'details',
-#    'state',
-    'groups',
-    'date',
-    'priority',
-    'occurrences',
-    'hostname',
-    'servicename',
-    'output',
-#    'masked_events',
-    'hls',
-    'status',
-#    'test',
-    'map',
-)
-
-base_config['csv_columns'] = (
-    'id',
-    'state',
-    'initial_state',
-    'peak_state',
-    'date',
-    'duration',
-    'priority',
-    'occurrences',
-    'hostname',
-    'servicename',
-    'output',
-    'ack',
-    'trouble_ticket_id',
-    'trouble_ticket_link',
-)
diff --git a/vigiboard/config/deployment.ini_tmpl b/vigiboard/config/deployment.ini_tmpl
deleted file mode 100644
index d307356..0000000
--- a/vigiboard/config/deployment.ini_tmpl
+++ /dev/null
@@ -1,98 +0,0 @@
-#
-# vigiboard - TurboGears configuration
-#
-# The %(here)s variable will be replaced with the parent directory of this file
-#
-# Copyright (C) 2006-2011 CS-SI
-# License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
-
-[DEFAULT]
-# WARGING == If debug is not set to false, you'll get the interactive
-# debugger on production, which is a huge security hole. 
-
-debug = false
-email_to = you@yourdomain.com
-smtp_server = localhost
-error_email_from = paste@localhost
-
-[server:main]
-use = egg:Paste#http
-host = 0.0.0.0
-port = 8080
-
-[app:main]
-use = egg:vigiboard
-full_stack = true
-cache_dir = %(here)s/data
-beaker.session.key = vigiboard
-beaker.session.secret = ${app_instance_secret}
-app_instance_uuid = ${app_instance_uuid}
-
-# If you'd like to fine-tune the individual locations of the cache data dirs
-# for the Cache data, or the Session saves, un-comment the desired settings
-# here:
-#beaker.cache.data_dir = %(here)s/data/cache
-#beaker.session.data_dir = %(here)s/data/sessions
-# Specify the database for SQLAlchemy to use via
-# turbogears.database
-# %(here) may include a ':' character on Windows environments; this can
-# invalidate the URI when specifying a SQLite db via path name
-sqlalchemy.url = sqlite:///%(here)s/somedb.db
-sqlalchemy.echo = False
-
-# WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*
-# Debug mode will enable the interactive debugging tool, allowing ANYONE to
-# execute malicious code after an exception is raised.
-#set debug = false
-
-# Logging configuration
-# Add additional loggers, handlers, formatters here
-# Uses python's logging config file format
-# http://docs.python.org/lib/logging-config-fileformat.html
-
-[loggers]
-keys = root, vigiboard, sqlalchemy, auth
-
-[handlers]
-keys = console
-
-[formatters]
-keys = generic
-
-# If you create additional loggers, add them as a key to [loggers]
-[logger_root]
-level = INFO
-handlers = console
-
-[logger_vigiboard]
-level = INFO
-handlers =
-qualname = vigiboard
-
-[logger_sqlalchemy]
-level = WARN
-handlers =
-qualname = sqlalchemy.engine
-# "level = INFO" logs SQL queries.
-# "level = DEBUG" logs SQL queries and results.
-# "level = WARN" logs neither.  (Recommended for production systems.)
-
-
-# A logger for authentication, identification and authorization -- this is
-# repoze.who and repoze.what:
-[logger_auth]
-level = WARN
-handlers = 
-qualname = auth
-
-# If you create additional handlers, add them as a key to [handlers]
-[handler_console]
-class = StreamHandler
-args = (sys.stderr,)
-level = NOTSET
-formatter = generic
-
-# If you create additional formatters, add them as a key to [formatters]
-[formatter_generic]
-format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
-datefmt = %H:%M:%S
diff --git a/vigiboard/config/environment.py b/vigiboard/config/environment.py
deleted file mode 100644
index 0688527..0000000
--- a/vigiboard/config/environment.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# -*- coding: utf-8 -*-
-################################################################################
-#
-# Copyright (C) 2007-2015 CS-SI
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 as
-# published by the Free Software Foundation.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-################################################################################
-
-"""WSGI environment setup for vigiboard."""
-from __future__ import absolute_import
-from .app_cfg import base_config
-
-__all__ = ['load_environment']
-
-#Use base_config to setup the environment loader function
-load_environment = base_config.make_load_environment()
diff --git a/vigiboard/config/middleware.py b/vigiboard/config/middleware.py
index 42c44b9..9d61b96 100644
--- a/vigiboard/config/middleware.py
+++ b/vigiboard/config/middleware.py
@@ -20,19 +20,14 @@
 
 """WSGI middleware initialization for the vigiboard application."""
 
-from vigiboard.config.app_cfg import base_config
-from vigiboard.config.environment import load_environment
-
+import imp
+import os.path
 from pkg_resources import resource_filename
 from paste.cascade import Cascade
 from paste.urlparser import StaticURLParser
 
 __all__ = ['make_app']
 
-# Use base_config to setup the necessary PasteDeploy application factory.
-# make_base_app will wrap the TG2 app with all the middleware it needs.
-make_base_app = base_config.setup_tg_wsgi_app(load_environment)
-
 
 def make_app(global_conf, full_stack=True, **app_conf):
     """
@@ -52,7 +47,15 @@ def make_app(global_conf, full_stack=True, **app_conf):
     @return: The vigiboard application with all the relevant middleware
         loaded.
     """
-    app = make_base_app(global_conf, full_stack=full_stack, **app_conf)
+    # Charge le fichier "app_cfg.py" se trouvant aux côtés de "settings.ini".
+    mod_info = imp.find_module('app_cfg', [ global_conf['here'] ])
+    app_cfg = imp.load_module('vigiboard.config.app_cfg', *mod_info)
+    base_config = app_cfg.base_config
+
+    # Initialisation de l'application et de son environnement d'exécution.
+    load_environment = base_config.make_load_environment()
+    make_base_app = base_config.setup_tg_wsgi_app(load_environment)
+    app = make_base_app(global_conf, full_stack=True, **app_conf)
 
     max_age = app_conf.get("cache_max_age")
     try:
@@ -60,8 +63,9 @@ def make_app(global_conf, full_stack=True, **app_conf):
     except (ValueError, TypeError):
         max_age = None
 
-    # Personalisation des fichiers statiques via /etc/vigilo/vigiboard/public/.
-    custom_static = StaticURLParser('/etc/vigilo/vigiboard/public/',
+    # Personalisation des fichiers statiques via un dossier public/
+    # dans le répertoire contenant le fichier settings.ini chargé.
+    custom_static = StaticURLParser(os.path.join(global_conf['here'], 'public'),
                                     cache_max_age=max_age)
 
     # On définit 2 middlewares pour fichiers statiques qui cherchent
diff --git a/vigiboard/websetup.py b/vigiboard/websetup.py
index f72e51c..ec37ac8 100644
--- a/vigiboard/websetup.py
+++ b/vigiboard/websetup.py
@@ -22,6 +22,8 @@
 # pylint: disable-msg=W0613
 # W0613: Unused arguments: on doit respecter l'API
 
+import imp
+
 __all__ = ['setup_app', 'populate_db']
 
 def _(msg):
@@ -36,8 +38,13 @@ def _(msg):
 def setup_app(command, conf, variables):
     """Place any commands to setup vigiboard here"""
     from vigilo.turbogears import populate_db as tg_pop_db
-    from vigiboard.config.environment import load_environment
 
+    # Charge le fichier "app_cfg.py" se trouvant aux côtés de "settings.ini".
+    mod_info = imp.find_module('app_cfg', [ conf.global_conf['here'] ])
+    app_cfg = imp.load_module('vigiboard.config.app_cfg', *mod_info)
+
+    # Initialisation de l'environnement d'exécution.
+    load_environment = app_cfg.base_config.make_load_environment()
     load_environment(conf.global_conf, conf.local_conf)
     tg_pop_db()
 
