514 lines
18 KiB
Python
514 lines
18 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Gestion du mini-langage de template
|
|
On travaille sur les fichiers cibles
|
|
"""
|
|
|
|
import imp
|
|
import sys
|
|
from shutil import copy
|
|
import logging
|
|
from typing import Dict, Any
|
|
|
|
from subprocess import call
|
|
from os import listdir, unlink, makedirs
|
|
from os.path import dirname, basename, join, split, isfile, isdir
|
|
|
|
from tempfile import mktemp
|
|
|
|
from Cheetah import Parser
|
|
# l'encoding du template est déterminé par une regexp (encodingDirectiveRE dans Parser.py)
|
|
# il cherche un ligne qui ressemble à '#encoding: utf-8
|
|
# cette classe simule le module 're' et retourne toujours l'encoding utf-8
|
|
# 6224
|
|
class FakeEncoding():
|
|
def groups(self):
|
|
return ('utf-8',)
|
|
|
|
def search(self, *args):
|
|
return self
|
|
Parser.encodingDirectiveRE = FakeEncoding()
|
|
|
|
from Cheetah.Template import Template as ChtTemplate
|
|
from Cheetah.NameMapper import NotFound as CheetahNotFound
|
|
|
|
from tiramisu import Config
|
|
from tiramisu.error import PropertiesOptionError
|
|
|
|
from .config import patch_dir
|
|
from .error import FileNotFound, TemplateError, TemplateDisabled
|
|
from .i18n import _
|
|
from .utils import normalize_family
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
log.addHandler(logging.NullHandler())
|
|
|
|
class IsDefined:
|
|
"""
|
|
filtre permettant de ne pas lever d'exception au cas où
|
|
la variable Creole n'est pas définie
|
|
"""
|
|
def __init__(self, context):
|
|
self.context = context
|
|
|
|
def __call__(self, varname):
|
|
if '.' in varname:
|
|
splitted_var = varname.split('.')
|
|
if len(splitted_var) != 2:
|
|
msg = _("Group variables must be of type master.slave")
|
|
raise KeyError(msg)
|
|
master, slave = splitted_var
|
|
if master in self.context:
|
|
return slave in self.context[master].slave.keys()
|
|
return False
|
|
else:
|
|
return varname in self.context
|
|
|
|
|
|
class CreoleGet:
|
|
def __init__(self, context):
|
|
self.context = context
|
|
|
|
def __call__(self, varname):
|
|
return self.context[varname]
|
|
|
|
def __getitem__(self, varname):
|
|
"""For bracket and dotted notation
|
|
"""
|
|
return self.context[varname]
|
|
|
|
def __contains__(self, varname):
|
|
"""Check variable existence in context
|
|
"""
|
|
return varname in self.context
|
|
|
|
|
|
@classmethod
|
|
def cl_compile(kls, *args, **kwargs):
|
|
kwargs['compilerSettings'] = {'directiveStartToken' : '%',
|
|
'cheetahVarStartToken' : '%%',
|
|
'EOLSlurpToken' : '%',
|
|
'PSPStartToken' : 'µ' * 10,
|
|
'PSPEndToken' : 'µ' * 10,
|
|
'commentStartToken' : 'µ' * 10,
|
|
'commentEndToken' : 'µ' * 10,
|
|
'multiLineCommentStartToken' : 'µ' * 10,
|
|
'multiLineCommentEndToken' : 'µ' * 10}
|
|
return kls.old_compile(*args, **kwargs)
|
|
ChtTemplate.old_compile = ChtTemplate.compile
|
|
ChtTemplate.compile = cl_compile
|
|
|
|
|
|
class CreoleClient:
|
|
def __init__(self,
|
|
config: Config):
|
|
self.config = config
|
|
|
|
|
|
class CheetahTemplate(ChtTemplate):
|
|
"""classe pour personnaliser et faciliter la construction
|
|
du template Cheetah
|
|
"""
|
|
def __init__(self,
|
|
filename: str,
|
|
context,
|
|
eosfunc: Dict,
|
|
config: Config,
|
|
current_container: str,
|
|
destfilename,
|
|
variable):
|
|
"""Initialize Creole CheetahTemplate
|
|
"""
|
|
extra_context = {'is_defined' : IsDefined(context),
|
|
# 'creole_client' : CreoleClient(config),
|
|
# 'current_container':CreoleGet(current_container),
|
|
'normalize_family': normalize_family,
|
|
'rougail_filename': destfilename
|
|
}
|
|
if variable:
|
|
extra_context['rougail_variable'] = variable
|
|
ChtTemplate.__init__(self,
|
|
file=filename,
|
|
searchList=[context, eosfunc, extra_context])
|
|
|
|
|
|
class CreoleLeader:
|
|
def __init__(self, value, slave=None, index=None):
|
|
"""
|
|
On rend la variable itérable pour pouvoir faire:
|
|
for ip in iplist:
|
|
print ip.network
|
|
print ip.netmask
|
|
print ip
|
|
index is used for CreoleLint
|
|
"""
|
|
self._value = value
|
|
if slave is not None:
|
|
self.slave = slave
|
|
else:
|
|
self.slave = {}
|
|
self._index = index
|
|
|
|
def __getattr__(self, name):
|
|
"""Get slave variable or attribute of master value.
|
|
|
|
If the attribute is a name of a slave variable, return its value.
|
|
Otherwise, returns the requested attribute of master value.
|
|
"""
|
|
if name in self.slave:
|
|
value = self.slave[name]
|
|
if isinstance(value, PropertiesOptionError):
|
|
raise AttributeError()
|
|
return value
|
|
else:
|
|
return getattr(self._value, name)
|
|
|
|
def __getitem__(self, index):
|
|
"""Get a master.slave at requested index.
|
|
"""
|
|
ret = {}
|
|
for key, values in self.slave.items():
|
|
ret[key] = values[index]
|
|
return CreoleLeader(self._value[index], ret, index)
|
|
|
|
def __iter__(self):
|
|
"""Iterate over master.slave.
|
|
|
|
Return synchronised value of master.slave.
|
|
"""
|
|
for i in range(len(self._value)):
|
|
ret = {}
|
|
for key, values in self.slave.items():
|
|
ret[key] = values[i]
|
|
yield CreoleLeader(self._value[i], ret, i)
|
|
|
|
def __len__(self):
|
|
"""Delegate to master value
|
|
"""
|
|
return len(self._value)
|
|
|
|
def __repr__(self):
|
|
"""Show CreoleLeader as dictionary.
|
|
|
|
The master value is stored under 'value' key.
|
|
The slaves are stored under 'slave' key.
|
|
"""
|
|
return repr({'value': self._value, 'slave': self.slave})
|
|
|
|
def __eq__(self, value):
|
|
return value == self._value
|
|
|
|
def __ne__(self, value):
|
|
return value != self._value
|
|
|
|
def __lt__(self, value):
|
|
return self._value < value
|
|
|
|
def __le__(self, value):
|
|
return self._value <= value
|
|
|
|
def __gt__(self, value):
|
|
return self._value > value
|
|
|
|
def __ge__(self, value):
|
|
return self._value >= value
|
|
|
|
def __str__(self):
|
|
"""Delegate to master value
|
|
"""
|
|
return str(self._value)
|
|
|
|
def __add__(self, val):
|
|
return self._value.__add__(val)
|
|
|
|
def __radd__(self, val):
|
|
return val + self._value
|
|
|
|
def __contains__(self, item):
|
|
return item in self._value
|
|
|
|
def add_slave(self, config, name, path):
|
|
if isinstance(self._value, list):
|
|
values = []
|
|
for idx in range(len(self._value)):
|
|
try:
|
|
values.append(config.option(path, idx).value.get())
|
|
except PropertiesOptionError as err:
|
|
values.append(err)
|
|
else:
|
|
raise Exception('hu?')
|
|
self.slave[name] = values
|
|
|
|
|
|
class CreoleExtra:
|
|
def __init__(self,
|
|
suboption: Dict) -> None:
|
|
self.suboption = suboption
|
|
|
|
def __getattr__(self,
|
|
key: str) -> Any:
|
|
return self.suboption[key]
|
|
|
|
def __repr__(self):
|
|
return self.suboption.__str__()
|
|
|
|
|
|
class CreoleTemplateEngine:
|
|
"""Engine to process Creole cheetah template
|
|
"""
|
|
def __init__(self,
|
|
config: Config,
|
|
eosfunc_file: str,
|
|
distrib_dir: str,
|
|
tmp_dir: str,
|
|
dest_dir:str) -> None:
|
|
self.config = config
|
|
self.dest_dir = dest_dir
|
|
self.tmp_dir = tmp_dir
|
|
self.distrib_dir = distrib_dir
|
|
eos = {}
|
|
eosfunc = imp.load_source('eosfunc', eosfunc_file)
|
|
for func in dir(eosfunc):
|
|
if not func.startswith('_'):
|
|
eos[func] = getattr(eosfunc, func)
|
|
self.eosfunc = eos
|
|
self.creole_variables_dict = {}
|
|
for option in self.config.option.list(type='all'):
|
|
namespace = option.option.name()
|
|
if namespace in ['containers', 'actions']:
|
|
continue
|
|
elif namespace == 'creole':
|
|
self.load_eole_variables_creole(self.config,
|
|
option)
|
|
else:
|
|
self.load_eole_variables(self.config,
|
|
namespace,
|
|
option)
|
|
|
|
def load_eole_variables_creole(self,
|
|
config,
|
|
optiondescription):
|
|
for option in optiondescription.list('all'):
|
|
if option.option.isoptiondescription():
|
|
if option.option.isleadership():
|
|
for idx, suboption in enumerate(option.list('all')):
|
|
if idx == 0:
|
|
leader = CreoleLeader(suboption.value.get())
|
|
self.creole_variables_dict[suboption.option.name()] = leader
|
|
else:
|
|
leader.add_slave(config,
|
|
suboption.option.name(),
|
|
suboption.option.path())
|
|
else:
|
|
self.load_eole_variables_creole(config,
|
|
option)
|
|
else:
|
|
self.creole_variables_dict[option.option.name()] = option.value.get()
|
|
|
|
def load_eole_variables(self,
|
|
config,
|
|
namespace,
|
|
optiondescription):
|
|
families = {}
|
|
for family in optiondescription.list('all'):
|
|
variables = {}
|
|
for variable in family.list('all'):
|
|
if variable.option.isoptiondescription() and variable.option.isleadership():
|
|
for idx, suboption in enumerate(variable.list('all')):
|
|
if idx == 0:
|
|
leader = CreoleLeader(suboption.value.get())
|
|
leader_name = suboption.option.name()
|
|
else:
|
|
leader.add_slave(config,
|
|
suboption.option.name(),
|
|
suboption.option.path())
|
|
variables[leader_name] = leader
|
|
else:
|
|
variables[variable.option.name()] = variable.value.get()
|
|
families[family.option.name()] = CreoleExtra(variables)
|
|
self.creole_variables_dict[namespace] = CreoleExtra(families)
|
|
|
|
def patch_template(self,
|
|
filename: str):
|
|
"""Apply patch to a template
|
|
"""
|
|
patch_cmd = ['patch', '-d', self.tmp_dir, '-N', '-p1']
|
|
patch_no_debug = ['-s', '-r', '-', '--backup-if-mismatch']
|
|
|
|
# patches variante + locaux
|
|
for directory in [join(patch_dir, 'variante'), patch_dir]:
|
|
patch_file = join(directory, f'{filename}.patch')
|
|
if isfile(patch_file):
|
|
log.info(_("Patching template '{filename}' with '{patch_file}'"))
|
|
ret = call(patch_cmd + patch_no_debug + ['-i', patch_file])
|
|
if ret:
|
|
patch_cmd_err = ' '.join(patch_cmd + ['-i', patch_file])
|
|
log.error(_(f"Error applying patch: '{patch_file}'\nTo reproduce and fix this error {patch_cmd_err}"))
|
|
copy(filename, self.tmp_dir)
|
|
|
|
def strip_template_comment(self,
|
|
filename: str):
|
|
"""Strip comment from template
|
|
|
|
This apply if filevar has a del_comment attribut
|
|
"""
|
|
# suppression des commentaires si demandé (attribut del_comment)
|
|
if 'del_comment' in filevar and filevar['del_comment'] != '':
|
|
strip_cmd = ['sed', '-i']
|
|
log.info(_("Cleaning file '{0}'").format( filevar['source'] ))
|
|
raise Exception('hu')
|
|
#ret, out, err = pyeole.process.system_out(strip_cmd
|
|
# + ['/^\s*{0}/d ; /^$/d'.format(filevar['del_comment']),
|
|
# filevar['source'] ])
|
|
#if ret != 0:
|
|
# msg = _("Error removing comments '{0}': {1}")
|
|
# raise TemplateError(msg.format(filevar['del_comment'], err))
|
|
|
|
def prepare_template(self,
|
|
filename: str):
|
|
"""Prepare template source file
|
|
"""
|
|
log.info(_("Copy template: '{filename}' -> '{self.tmp_dir}'"))
|
|
copy(filename, self.tmp_dir)
|
|
self.patch_template(filename)
|
|
# self.strip_template_comment(filename)
|
|
|
|
def process(self,
|
|
destfilename: str,
|
|
filevar: Dict,
|
|
container: str,
|
|
variable: Any):
|
|
"""Process a cheetah template
|
|
"""
|
|
# full path of the destination file
|
|
log.info(_(f"Cheetah processing: '{destfilename}'"))
|
|
try:
|
|
cheetah_template = CheetahTemplate(join(self.tmp_dir,
|
|
filevar['source']),
|
|
self.creole_variables_dict,
|
|
self.eosfunc,
|
|
self.config.config.copy(),
|
|
container,
|
|
destfilename,
|
|
variable)
|
|
data = str(cheetah_template)
|
|
except CheetahNotFound as err:
|
|
varname = err.args[0][13:-1]
|
|
raise TemplateError(_(f"Error: unknown variable used in template {destfilename} : {varname}"))
|
|
except Exception as err:
|
|
raise TemplateError(_(f"Error while instantiating template {destfilename}: {err}"))
|
|
|
|
with open(destfilename, 'w') as file_h:
|
|
file_h.write(data)
|
|
|
|
def change_properties(self,
|
|
destfilename:str,
|
|
filevar: Dict):
|
|
#chowncmd = ['chown']
|
|
#chownarg = ''
|
|
chmodcmd = ['chmod']
|
|
chmodarg = ''
|
|
|
|
#if 'owner' in filevar and filevar['owner']:
|
|
# chownarg = filevar['owner']
|
|
#else:
|
|
# chownarg = 'root'
|
|
|
|
#if 'group' in filevar and filevar['group']:
|
|
# chownarg += ":" + filevar['group']
|
|
#else:
|
|
# chownarg += ':root'
|
|
|
|
if 'mode' in filevar and filevar['mode']:
|
|
chmodarg = filevar['mode']
|
|
else:
|
|
chmodarg = '0644'
|
|
|
|
#chowncmd.extend( [chownarg, destfilename] )
|
|
chmodcmd.extend([chmodarg, destfilename])
|
|
|
|
#log.info(_('Changing properties: {0}').format(' '.join(chowncmd)) )
|
|
#ret = call(chowncmd)
|
|
#if ret:
|
|
# log.error(_('Error changing properties {0}: {1}').format(ret, err) )
|
|
|
|
log.info(_('Changing properties: {0}').format(' '.join(chmodcmd)) )
|
|
ret = call(chmodcmd)
|
|
if ret:
|
|
chmod_cmd = ' '.join(chmodcmd)
|
|
log.error(_(f'Error changing properties: {chmodcmd}'))
|
|
|
|
def instance_file(self,
|
|
filevar: Dict,
|
|
container: str):
|
|
"""Run templatisation on one file of one container
|
|
"""
|
|
log.info(_("Instantiating file '{filename}'"))
|
|
container_dir = join(self.dest_dir,
|
|
container)
|
|
filenames = filevar['name']
|
|
if 'variable' in filevar:
|
|
variable = filevar['variable']
|
|
else:
|
|
variable = None
|
|
if not isinstance(filenames, list):
|
|
filenames = [filenames]
|
|
if variable:
|
|
variable = [variable]
|
|
for idx, filename in enumerate(filenames):
|
|
destfilename = join(container_dir,
|
|
filename[1:])
|
|
makedirs(dirname(destfilename), exist_ok=True)
|
|
if variable:
|
|
var = variable[idx]
|
|
else:
|
|
var = None
|
|
self.process(destfilename,
|
|
filevar,
|
|
container,
|
|
var)
|
|
self.change_properties(destfilename,
|
|
filevar)
|
|
|
|
def instance_files(self,
|
|
container=None):
|
|
"""Run templatisation on all files of all containers
|
|
|
|
@param container: name of a container
|
|
@type container: C{str}
|
|
"""
|
|
for template in listdir(self.distrib_dir):
|
|
self.prepare_template(join(self.distrib_dir, template))
|
|
for container_obj in self.config.option('containers').list('all'):
|
|
current_container = container_obj.option.doc()
|
|
if container is not None and container != current_container:
|
|
continue
|
|
for fills in container_obj.list('all'):
|
|
if fills.option.name() == 'files':
|
|
for fill_obj in fills.list('all'):
|
|
fill = fill_obj.value.dict()
|
|
filename = fill['source']
|
|
distib_file = join(self.distrib_dir, filename)
|
|
if not isfile(distib_file):
|
|
raise FileNotFound(_(f"File {distib_file} does not exist."))
|
|
if fill.get('activate', False):
|
|
self.instance_file(fill,
|
|
current_container)
|
|
else:
|
|
log.debug(_("Instantiation of file '{filename}' disabled"))
|
|
|
|
|
|
def generate(config: Config,
|
|
eosfunc_file: str,
|
|
distrib_dir: str,
|
|
tmp_dir: str,
|
|
dest_dir: str,
|
|
container: str=None):
|
|
engine = CreoleTemplateEngine(config,
|
|
eosfunc_file,
|
|
distrib_dir,
|
|
tmp_dir,
|
|
dest_dir)
|
|
engine.instance_files(container=container)
|