
- Meilleurs gestion des evenements sur les fichiers - Gestion des inclusions multiples dans les scripts darcs-hash:20071218042641-af139-4c4ba1698a4af5367419262e1bf8080be6c3aa20.gz
244 lines
9.1 KiB
Python
244 lines
9.1 KiB
Python
# -*- mode: python; coding: utf-8 -*-
|
|
#
|
|
# Python.py
|
|
# ---------
|
|
#
|
|
# Copyright (C) 2007 Jeremie Dimino <jeremie@dimino.org>
|
|
#
|
|
# This file is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This file 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., 59 Temple Street #330, Boston, MA 02111-1307, USA.
|
|
|
|
''' Plugin pour bcfg2 pour générer des fichiers de conf en prenant la sortie d'un
|
|
script python'''
|
|
|
|
__all__ = ["Python"]
|
|
|
|
import logging, lxml.etree, posixpath, re, os, sys
|
|
from cStringIO import StringIO
|
|
import Bcfg2.Server.Plugin
|
|
|
|
sys.path.append('/usr/scripts/bcfg2')
|
|
import pygen
|
|
|
|
logger = logging.getLogger('Bcfg2.Plugins.Python')
|
|
|
|
# Helper pour le deboggage
|
|
if os.getenv("BCFG2_DEBUG"):
|
|
debug_colored = os.getenv("BCFG2_DEBUG_COLOR")
|
|
color_code = {'grey': 30,
|
|
'red': 31,
|
|
'green': 32,
|
|
'yellow': 33,
|
|
'blue': 34,
|
|
'purple': 35,
|
|
'cyan': 36}
|
|
def debug(msg, color=None):
|
|
if debug_colored and color:
|
|
logger.info("\033[1;%dm%s\033[0m" % (color_code[color], msg))
|
|
else:
|
|
logger.info(msg)
|
|
else:
|
|
def debug(msg, color=None):
|
|
pass
|
|
|
|
# Les fichiers qui sont surveillés par le plugin dans les dossier /etc/../machin.cf/
|
|
_monitored_files = ["info.xml", "gen.py"]
|
|
|
|
# Dico nom de fichier -> code
|
|
includes = {}
|
|
|
|
def dump(env, incfile):
|
|
exec(includes[incfile], env)
|
|
|
|
def include(env, incfile):
|
|
if not incfile in env.included:
|
|
env.included.add(incfile)
|
|
exec(includes[incfile], env)
|
|
|
|
def load_file(filename):
|
|
'''Charge un script et affiche un message d'erreur en cas d'exception'''
|
|
try:
|
|
return pygen.load(filename)
|
|
except Exception, e:
|
|
logger.error('Python compilation error: %s: %s' % (str(e.__class__).split('.', 2)[1], str(e)))
|
|
return None
|
|
|
|
class PythonFile:
|
|
'''Template file creates Python template structures for the loaded file'''
|
|
def __init__(self, name, properties):
|
|
self.name = name
|
|
self.info = None
|
|
self.code = None
|
|
self.properties = properties
|
|
|
|
def Created(self, path, event):
|
|
'''Traitement des créations de fichier'''
|
|
if event.filename == "gen.py":
|
|
self.code = load_file(path)
|
|
else:
|
|
self.info = Bcfg2.Server.Plugin.XMLSrc(path, True)
|
|
self.info.HandleEvent(event)
|
|
|
|
def Changed(self, path, event):
|
|
'''Traitement des modifications de fichier'''
|
|
if event.filename == "gen.py":
|
|
self.code = load_file(path)
|
|
else:
|
|
self.info.HandleEvent(event)
|
|
|
|
def Deleted(self, path, event):
|
|
'''Traitement des suppressions de fichier'''
|
|
if event.filename == "gen.py":
|
|
self.code = None
|
|
else:
|
|
self.info = None
|
|
|
|
def BuildFile(self, entry, metadata):
|
|
'''Build literal file information'''
|
|
fname = entry.get('realname', entry.get('name'))
|
|
debug("building config file: %s" % fname, 'blue')
|
|
try:
|
|
env = pygen.Environment()
|
|
env["metadata"] = metadata
|
|
env["properties"] = self.properties
|
|
env["include"] = lambda incfile: include(env, incfile)
|
|
env["dump"] = lambda incfile: dump(env, incfile)
|
|
env.included = set([])
|
|
include(env, "common")
|
|
entry.text = pygen.generate(self.code, env)
|
|
debug(entry.text)
|
|
except Exception, e:
|
|
logger.error('Python exec error: %s: %s' % (str(e.__class__).split('.', 2)[1], str(e)))
|
|
raise Bcfg2.Server.Plugin.PluginExecutionError
|
|
if self.info:
|
|
mdata = {}
|
|
self.info.pnode.Match(metadata, mdata)
|
|
mdata = mdata['Info'][None]
|
|
[entry.attrib.__setitem__(key, value)
|
|
for (key, value) in mdata.iteritems()]
|
|
|
|
class PythonProperties(Bcfg2.Server.Plugin.SingleXMLFileBacked):
|
|
'''Class for Python properties'''
|
|
def Index(self):
|
|
'''Build data into an elementtree object for templating usage'''
|
|
try:
|
|
self.properties = lxml.etree.XML(self.data)
|
|
del self.data
|
|
except lxml.etree.XMLSyntaxError:
|
|
logger.error("Failed to parse properties")
|
|
|
|
class FakeProperties:
|
|
'''Dummy class used when properties dont exist'''
|
|
def __init__(self):
|
|
self.properties = lxml.etree.Element("Properties")
|
|
|
|
class Python(Bcfg2.Server.Plugin.Plugin):
|
|
'''The Python generator implements a templating mechanism for configuration files'''
|
|
__name__ = 'Python'
|
|
__version__ = '1.0'
|
|
__author__ = 'dimino@crans.org'
|
|
|
|
def __init__(self, core, datastore):
|
|
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
|
|
# Les entrées pour bcfg2
|
|
self.Entries['ConfigFile'] = {}
|
|
# Nos entrées pour nous (dico de PythonFile)
|
|
self.entries = {}
|
|
# Dico ID de requête GAM -> Dossier surveillé
|
|
self.handles = {}
|
|
# Le dossier qui contient les fichiers à inclure
|
|
self.include = os.path.abspath(self.data + '/../etc/python')
|
|
|
|
self.AddDirectoryMonitor(self.data)
|
|
self.AddDirectoryMonitor(self.include)
|
|
|
|
try:
|
|
self.properties = PythonProperties('%s/../etc/properties.xml' \
|
|
% (self.data), self.core.fam)
|
|
except:
|
|
self.properties = FakeProperties()
|
|
self.logger.info("Failed to read properties file; Python properties disabled")
|
|
|
|
def BuildEntry(self, entry, metadata):
|
|
'''Dispatch fetch calls to the correct object'''
|
|
self.entries[entry.get('name')].BuildFile(entry, metadata)
|
|
|
|
def HandleEvent(self, event):
|
|
'''Traitement des événements de FAM'''
|
|
action = event.code2str()
|
|
debug("event received: action=%s filename=%s requestID=%s" %
|
|
(action, event.filename, event.requestID), 'purple')
|
|
|
|
if event.filename[0] == '/' or event.filename.endswith(".pyc"):
|
|
# Rien d'intéressant
|
|
return
|
|
|
|
hdle_path = self.handles[event.requestID]
|
|
path = hdle_path + "/" + event.filename
|
|
debug("absolute filename: %s" % path, 'yellow')
|
|
|
|
if path.startswith(self.include):
|
|
if posixpath.isfile(path) and path.endswith(".py"):
|
|
# Les fichiers d'includes...
|
|
identifier = path[len(self.include)+1:-3]
|
|
if action in ['exists', 'created', 'changed']:
|
|
debug("adding include file: %s" % identifier, 'green')
|
|
includes[identifier] = load_file(path)
|
|
elif action == 'deleted':
|
|
debug("deleting include file: %s" % identifier, 'red')
|
|
del includes[identifier]
|
|
elif posixpath.isdir(path) and action in ['exists', 'created']:
|
|
self.AddDirectoryMonitor(path)
|
|
|
|
elif posixpath.isfile(path) and event.filename in _monitored_files:
|
|
# Le nom du dossier est le nom du fichier du fichier de
|
|
# configuration à générer
|
|
identifier = hdle_path[len(self.data):]
|
|
if action in ['exists', 'created']:
|
|
if not self.entries.has_key(identifier):
|
|
entry = PythonFile(identifier, self.properties)
|
|
entry.file_count = 1
|
|
debug("adding config file: %s" % identifier, 'green')
|
|
self.entries[identifier] = entry
|
|
self.Entries['ConfigFile'][identifier] = self.BuildEntry
|
|
else:
|
|
entry = self.entries[identifier]
|
|
entry.file_count += 1
|
|
entry.Created(path, event)
|
|
elif action == 'changed':
|
|
self.entries[identifier].Changed(path, event)
|
|
elif action == 'deleted':
|
|
entry = self.entries[identifier]
|
|
entry.Deleted(path, event)
|
|
entry.file_count -= 1
|
|
if not entry.file_count:
|
|
debug("deleting config file: %s" % identifier, 'red')
|
|
del self.entries[identifier]
|
|
del self.Entries['ConfigFile'][identifier]
|
|
|
|
elif posixpath.isdir(path):
|
|
if action in ['exists', 'created']:
|
|
self.AddDirectoryMonitor(path)
|
|
|
|
else:
|
|
logger.info('Ignoring file %s' % path)
|
|
|
|
def AddDirectoryMonitor(self, path):
|
|
'''Surveille un dossier avec FAM'''
|
|
if path not in self.handles.values():
|
|
if not posixpath.isdir(path):
|
|
print "Python: Failed to open directory %s" % (name)
|
|
return
|
|
reqid = self.core.fam.AddMonitor(path, self)
|
|
self.handles[reqid] = path
|