nutools/lib/ulib/support/deploydb.py

1259 lines
48 KiB
Python
Raw Normal View History

#!/usr/bin/env python
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
import os, sys, re
from os import path
from argparse import ArgumentParser, REMAINDER
from glob import glob
USER_CONFDIR = '~/etc/deploy'
SYSTEM_CONFDIR = '/var/local/deploy'
2017-06-18 21:59:38 +04:00
#XXX faire la différence entre "pas de profil défini" (=aucun profil) et
# "définition par défaut pour les profils" (si pas de définition plus précise
# pour un profil, prendre celle-là)
2017-06-18 11:57:38 +04:00
################################################################################
# Diverses fonctions
def isseq(t):
return isinstance(t, list) or isinstance(t, tuple) or isinstance(t, set)
################################################################################
# Base de données
class GenericObject(object):
_id = None
_values = None
_attrs = None
def __init__(self, id):
self._id = id
2017-06-18 11:57:38 +04:00
self._values = set()
self._attrs = {}
def get_id(self): return self._id
id = property(get_id)
def get_values(self): return self._values
values = property(get_values)
def add_value(self, value):
2017-06-18 11:57:38 +04:00
self._values.add(value)
def get_attrs(self): return self._attrs
attrs = property(get_attrs)
def get_attr(self, name): return self._attrs.get(name, None)
def set_attr(self, name, value):
if name not in self._attrs: self._attrs[name] = []
if value not in self._attrs[name]: self._attrs[name].append(value)
def reset_attr(self, name, value=None):
if name in self._attrs: del self._attrs[name]
if value is not None: self.set_attr(name, value)
def add_attr(self, name, value):
if name not in self._attrs: self._attrs[name] = []
self._attrs[name].append(value)
def del_attr(self, name, value):
if name not in self._attrs: return
self._attrs[name].remove(value)
if not self._attrs[name]: del self._attrs[name]
def modify_attr(self, name, value, method='set'):
if method == 'set': self.set_attr(name, value)
elif method == 'reset': self.reset_attr(name, value)
elif method == 'add': self.add_attr(name, value)
elif method == 'del': self.del_attr(name, value)
2017-06-16 00:36:36 +04:00
def merge_attrs(self, other, method='set'):
for name, values in other.attrs.items():
for value in values:
self.modify_attr(name, value, method)
def _dump_id(self, indent):
print "%s%s" % (indent, self.id)
def _dump_values(self, indent, name):
print "%s %s: %s" % (indent, name, ' '.join(self.values))
def _dump_attrs(self, indent):
if self.attrs:
print "%s attrs:" % indent
for name, values in self.attrs.items():
if len(values) == 1:
print "%s %s=%s" % (indent, name, repr(values[0]))
else:
print "%s %s=(%s)" % (indent, name, ', '.join(map(repr, values)))
def dump(self, indent=''):
self._dump_id(indent)
self._dump_attrs(indent)
class HostObject(GenericObject):
@staticmethod
def genid(host):
"MY-Host42.self --> my_host42"
host = re.sub(r'\..*', '', host)
host = re.sub(r'[^a-zA-Z0-9]', '_', host)
host = host.lower()
return host
@staticmethod
def genib(id):
"my_host42 --> my_host"
if re.match(r'\d+$', id): return id
ib = re.sub(r'^(.*?)\d+$', r'\1', id)
return ib
def dump(self, indent=''):
self._dump_id(indent)
self._dump_values(indent, 'hosts')
self._dump_attrs(indent)
class ModuleObject(GenericObject):
@staticmethod
def genid(module):
"MY-Module --> MY_Module"
module = re.sub(r'[^a-zA-Z0-9]', '_', module)
return module
def dump(self, indent=''):
self._dump_id(indent)
self._dump_values(indent, 'modules')
self._dump_attrs(indent)
class WobundleObject(GenericObject):
@staticmethod
def genid(wobundle):
"MY-App.woa --> MY_App"
wobundle = re.sub(r'\.(woa|framework)$', '', wobundle)
wobundle = re.sub(r'[^a-zA-Z0-9]', '_', wobundle)
return wobundle
def dump(self, indent=''):
self._dump_id(indent)
self._dump_values(indent, 'wobundles')
self._dump_attrs(indent)
class WebappObject(GenericObject):
@staticmethod
def genid(webapp):
"MY-Webapp --> MY_Webapp"
webapp = re.sub(r'[^a-zA-Z0-9]', '_', webapp)
return webapp
def dump(self, indent=''):
self._dump_id(indent)
self._dump_values(indent, 'webapps')
self._dump_attrs(indent)
2017-06-16 00:36:36 +04:00
class GenericLink(object):
_profile = None
_ftype = None
2017-06-18 11:57:38 +04:00
_fos = None
2017-06-16 00:36:36 +04:00
_ttype = None
_tos = None
_attrs = None
def __init__(self, ftype, ttype):
self._ftype = ftype
2017-06-18 11:57:38 +04:00
self._fos = set()
2017-06-16 00:36:36 +04:00
self._ttype = ttype
2017-06-18 11:57:38 +04:00
self._tos = set()
2017-06-16 00:36:36 +04:00
self._attrs = {}
2017-06-18 21:59:38 +04:00
def is_defaults(self):
fos = list(self._fos)
return len(fos) == 1 and fos[0] is None
2017-06-16 00:36:36 +04:00
def get_profile(self): return self._profile
2017-06-18 21:59:38 +04:00
def set_profile(self, profile): self._profile = profile
2017-06-16 00:36:36 +04:00
profile = property(get_profile, set_profile)
def get_ftype(self): return self._ftype
ftype = property(get_ftype)
2017-06-18 11:57:38 +04:00
def get_fos(self): return self._fos
fos = property(get_fos)
def add_fo(self, fo):
self._fos.add(fo)
2017-06-16 00:36:36 +04:00
def get_ttype(self): return self._ttype
ttype = property(get_ttype)
def get_tos(self): return self._tos
tos = property(get_tos)
def add_to(self, to):
2017-06-18 11:57:38 +04:00
self._tos.add(to)
2017-06-16 00:36:36 +04:00
def get_attrs(self): return self._attrs
attrs = property(get_attrs)
def get_attr(self, name): return self._attrs.get(name, None)
def set_attr(self, name, value):
if name not in self._attrs: self._attrs[name] = []
if value not in self._attrs[name]: self._attrs[name].append(value)
def reset_attr(self, name, value=None):
if name in self._attrs: del self._attrs[name]
if value is not None: self.set_attr(name, value)
def add_attr(self, name, value):
if name not in self._attrs: self._attrs[name] = []
self._attrs[name].append(value)
def del_attr(self, name, value):
if name not in self._attrs: return
self._attrs[name].remove(value)
if not self._attrs[name]: del self._attrs[name]
def modify_attr(self, name, value, method='set'):
if method == 'set': self.set_attr(name, value)
elif method == 'reset': self.reset_attr(name, value)
elif method == 'add': self.add_attr(name, value)
elif method == 'del': self.del_attr(name, value)
def merge_attrs(self, other, method='set'):
for name, values in other.attrs.items():
for value in values:
self.modify_attr(name, value, method)
2017-06-18 21:59:38 +04:00
def match_profiles(self, profiles):
if not isseq(profiles): profiles = [profiles]
for profile in profiles:
if profile == self._profile: return True
return False
2017-06-18 11:57:38 +04:00
def match_fos(self, fos, match='any'):
if not isseq(fos): fos = [fos]
for fo in fos:
if fo in self._fos: return True
return False
def match_tos(self, tos, match='any'):
if not isseq(tos): tos = [tos]
for to in tos:
if to in self._tos: return True
return False
def match_attrs(self, attrs, match='any'):
for name, value in attrs.items():
values = self._attrs.get(name, None)
if values is not None:
if value in values: return True
return False
2017-06-16 00:36:36 +04:00
def _dump_profile(self, indent):
profile = self.profile or 'ALL'
print "%s profile: %s" % (indent, profile)
2017-06-18 11:57:38 +04:00
def _dump_fos(self, indent):
2017-06-18 21:59:38 +04:00
fos = [fo or '(default)' for fo in self.fos]
print "%s from: %s" % (indent, ' '.join(fos))
2017-06-16 00:36:36 +04:00
def _dump_tos(self, indent):
print "%s to: %s" % (indent, ' '.join(self.tos))
def _dump_attrs(self, indent):
if self.attrs:
print "%s attrs:" % indent
for name, values in self.attrs.items():
if len(values) == 1:
print "%s %s=%s" % (indent, name, repr(values[0]))
else:
print "%s %s=(%s)" % (indent, name, ', '.join(map(repr, values)))
def dump(self, indent=''):
print "%slink" % indent
self._dump_profile(indent)
2017-06-18 11:57:38 +04:00
self._dump_fos(indent)
2017-06-16 00:36:36 +04:00
self._dump_tos(indent)
self._dump_attrs(indent)
class UinstLink(GenericLink):
def __init__(self):
super(UinstLink, self).__init__('module', 'host')
def dump(self, indent=''):
print "%suinst" % indent
self._dump_profile(indent)
2017-06-18 11:57:38 +04:00
self._dump_fos(indent)
2017-06-16 00:36:36 +04:00
self._dump_tos(indent)
self._dump_attrs(indent)
class RuinstLink(GenericLink):
def __init__(self):
2017-06-18 11:57:38 +04:00
super(RuinstLink, self).__init__('module', 'host')
2017-06-16 00:36:36 +04:00
def dump(self, indent=''):
2017-06-18 21:59:38 +04:00
print "%sruinst" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RwoinstBundleLink(GenericLink):
def __init__(self):
super(RwoinstBundleLink, self).__init__('wobundle', 'host')
def dump(self, indent=''):
print "%srwoinst bundle" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RwoinstWebresLink(GenericLink):
def __init__(self):
super(RwoinstWebresLink, self).__init__('wobundle', 'host')
def dump(self, indent=''):
print "%srwoinst webres" % indent
self._dump_profile(indent)
self._dump_fos(indent)
self._dump_tos(indent)
self._dump_attrs(indent)
class RtoinstLink(GenericLink):
def __init__(self):
super(RtoinstLink, self).__init__('webapp', 'host')
def dump(self, indent=''):
print "%srtoinst" % indent
self._dump_profile(indent)
2017-06-18 11:57:38 +04:00
self._dump_fos(indent)
2017-06-16 00:36:36 +04:00
self._dump_tos(indent)
self._dump_attrs(indent)
2017-06-18 21:59:38 +04:00
class UNDEF(object):
__repr__ = __string__ = lambda self: 'UNDEF'
UNDEF = UNDEF()
class Database(object):
_default_profile = None
_default_domain = None
2017-06-18 21:59:38 +04:00
_objects_ids_otype = None
_objects_classes = None
2017-06-18 21:59:38 +04:00
_links_ltype = None
2017-06-16 00:36:36 +04:00
_links_classes = None
def __init__(self):
2017-06-18 21:59:38 +04:00
self._objects_ids_otype = {}
self._objects_classes = {}
2017-06-18 21:59:38 +04:00
self._links_ltype = {}
2017-06-16 00:36:36 +04:00
self._links_classes = {}
def has_default_profile(self):
return self._default_profile is not None
def get_default_profile(self):
return self._default_profile
def set_default_profile(self, profile):
self._default_profile = profile or None
default_profile = property(get_default_profile, set_default_profile)
def has_default_domain(self):
return self._default_domain is not None
def get_default_domain(self):
return self._default_domain
def set_default_domain(self, domain):
if domain is not None:
#XXX si le domaine a été corrigé, l'indiquer en warning
if domain.startswith('.'): domain = domain[1:]
if domain.endswith('.'): domain = domain[:-1]
self._default_domain = domain or None
default_domain = property(get_default_domain, set_default_domain)
def register_object(self, otype, oclass=None):
2017-06-18 21:59:38 +04:00
if not self._objects_ids_otype.has_key(otype):
self._objects_ids_otype[otype] = {}
if oclass is None: oclass = GenericObject
self._objects_classes[otype] = oclass
2017-06-18 21:59:38 +04:00
def get_known_otypes(self):
return self._objects_ids_otype.keys()
known_otypes = property(get_known_otypes)
def get_objects(self, otype):
2017-06-18 21:59:38 +04:00
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return None
return [objects[id] for id in objects if id is not None]
def has_object(self, otype, id):
2017-06-18 21:59:38 +04:00
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return False
return objects.has_key(id)
def get_object(self, otype, id):
2017-06-18 21:59:38 +04:00
objects = self._objects_ids_otype.get(otype, None)
if objects is None: return None
object = objects.get(id, None)
if object is None:
object_class = self._objects_classes.get(otype, None)
if object_class is not None:
object = object_class(id)
2017-06-18 21:59:38 +04:00
self._objects_ids_otype[otype][id] = object
return object
2017-06-18 11:57:38 +04:00
def register_link(self, ltype, lclass):
2017-06-18 21:59:38 +04:00
if not self._links_ltype.has_key(ltype):
self._links_ltype[ltype] = []
2017-06-16 00:36:36 +04:00
self._links_classes[ltype] = lclass
2017-06-18 21:59:38 +04:00
def get_known_ltypes(self):
return self._links_ltype.keys()
known_ltypes = property(get_known_ltypes)
def get_links(self, ltype, profile=UNDEF, fo=UNDEF, to=UNDEF, attrs=UNDEF, create=False):
links = self._links_ltype.get(ltype, None)
if links is None: return None
2017-06-18 11:57:38 +04:00
found = None
for link in links:
2017-06-18 21:59:38 +04:00
if (profile is UNDEF or link.match_profiles(profile)) \
and (fo is UNDEF or link.match_fos(fo)) \
and (to is UNDEF or link.match_tos(to)) \
and (attrs is UNDEF or link.match_attrs(attrs)):
2017-06-18 11:57:38 +04:00
if found is None: found = []
found.append(link)
if found is not None: return found
2017-06-18 21:59:38 +04:00
if not create: return []
# ici, create==True
if profile is UNDEF: profile = None
2017-06-18 11:57:38 +04:00
link = self._links_classes[ltype]()
2017-06-18 21:59:38 +04:00
link.profile = profile
if fo is not UNDEF:
2017-06-18 11:57:38 +04:00
if isseq(fo): fos = fo
else: fos = [fo]
for fo in fos:
link.add_fo(fo)
2017-06-18 21:59:38 +04:00
if to is not UNDEF:
2017-06-18 11:57:38 +04:00
if isseq(to): tos = to
else: tos = [to]
for to in tos:
link.add_to(to)
2017-06-18 21:59:38 +04:00
if attrs is not UNDEF:
2017-06-18 11:57:38 +04:00
for name, value in attrs.items():
link.set_attr(name, value)
links.append(link)
return [link]
2017-06-16 00:36:36 +04:00
################################################################################
# Analyse des fichiers de configuration
class EOL(object):
__repr__ = __string__ = lambda self: 'EOL'
EOL = EOL()
class EOF(object):
__repr__ = __string__ = lambda self: 'EOF'
EOF = EOF()
class Lexer(object):
file = None
lexems = None
_inf = None
_lcount = None
_line = None
def __init__(self, file, parse=True):
self.file = file
if parse: self.parse()
def next_line(self):
line = self._inf.readline()
if line == '': return None
if line.endswith("\r\n"): line = line[:-2]
elif line.endswith("\n"): line = line[:-1]
elif line.endswith("\r"): line = line[:-1]
self._lcount += 1
self._line = line
return line
def is_empty(self): return self._line == ''
def isa_comment(self): return self._line[:1] == '#'
def isa_squote(self): return self._line[:1] == "'"
def isa_dquote(self): return self._line[:1] == '"'
RE_SPACE = re.compile(r'\s+')
RE_COMMENT = re.compile(r'#.*')
def parse_ws(self):
mo = self.RE_SPACE.match(self._line)
if mo is not None:
self._line = self._line[mo.end(0):]
mo = self.RE_COMMENT.match(self._line)
if mo is not None:
self._line = self._line[mo.end(0):]
def isa_space(self): return self.RE_SPACE.match(self._line) is not None
def isa_comment(self): return self.RE_COMMENT.match(self._line) is not None
RE_SQUOTE = re.compile(r"'")
def parse_sstring(self):
slos = self._lcount
lexem = ''
self._line = self._line[1:]
mo = self.RE_SQUOTE.search(self._line)
while mo is None:
lexem += self._line
if self.next_line() is None:
raise ValueError("unterminated quoted string starting at line %i" % slos)
lexem += "\n"
mo = self.RE_SQUOTE.search(self._line)
lexem += self._line[0:mo.start(0)]
self._line = self._line[mo.end(0):]
return lexem
RE_DQUOTE = re.compile(r'"')
def parse_dstring(self):
slos = self._lcount
lexem = ''
self._line = self._line[1:]
mo = self.RE_DQUOTE.search(self._line)
while mo is None:
lexem += self._line
if self.next_line() is None:
raise ValueError("unterminated double-quoted string starting at line %i" % slos)
lexem += "\n"
mo = self.RE_DQUOTE.search(self._line)
lexem += self._line[0:mo.start(0)]
self._line = self._line[mo.end(0):]
lexem = lexem.replace('\\"', '"')
lexem = lexem.replace("\\'", "'")
lexem = lexem.replace('\\\\', '\\')
return lexem
RE_EOS = re.compile(r'''\s|(?<!\\)['"]''')
def parse_string(self):
mo = self.RE_EOS.search(self._line)
if mo is not None:
lexem = self._line[0:mo.start(0)]
self._line = self._line[mo.start(0):]
else:
lexem = self._line
self._line = ''
lexem = lexem.replace('\\"', '"')
lexem = lexem.replace("\\'", "'")
lexem = lexem.replace('\\\\', '\\')
return lexem
def parse(self):
if self.lexems is not None: return self.lexems
lexems = self.lexems = []
self._inf = open(self.file, 'rb')
self._lcount = 0
self._line = ''
try:
SOL = False
while True:
# Ignorer lignes vides et commentaires
self.parse_ws()
stop = False
while self.is_empty():
if SOL:
lexems.append(EOL)
SOL = False
if self.next_line() is None:
stop = True
break
self.parse_ws()
if stop: break
SOL = True
# Construire une chaine
lexem = ''
while True:
if self.is_empty(): break
elif self.isa_space(): break
if self.isa_squote(): lexem += self.parse_sstring()
elif self.isa_dquote(): lexem += self.parse_dstring()
else: lexem += self.parse_string()
lexems.append(lexem)
lexems.append(EOF)
return lexems
finally:
self._inf.close()
self._inf = None
return lexems
def get_predicates(self):
predicates = []
predicate = []
for lexem in self.parse():
if lexem is EOF: break
elif lexem is EOL:
predicates.append(predicate)
predicate = []
else:
predicate.append(lexem)
return predicates
RE_NAMETYPE = re.compile(r'(\S+):(\w+)$')
def split_namev(arg):
"spliter name[:type][method][=value]"
if '=' in arg:
name, value = arg.split('=', 1)
else:
name = arg
value = None
if name.endswith('%'):
name = name[:-1]
2017-06-15 02:24:17 +04:00
method = 'reset'
elif name.endswith('+'):
name = name[:-1]
method = 'add'
elif name.endswith('-'):
name = name[:-1]
method = 'del'
else:
2017-06-15 02:24:17 +04:00
method = 'set'
mo = RE_NAMETYPE.match(name)
if mo is not None:
name, type = mo.groups()
else:
type = None
return name, value, type, method
def split_nvalue(arg):
"spliter [name=]value"
if '=' in arg:
name, value = arg.split('=', 1)
else:
name = None
value = arg
return name, value
def split_nlist(arg):
"spliter [name=]values"
if '=' in arg:
name, values = arg.split('=', 1)
values = values.split(',')
else:
name = None
values = arg.split(',')
return name, values
class Parser(object):
db = None
groups = None
attr_otype = None
commands = None
def __init__(self, db=None):
if db is None: db = Database()
self.db = db
self.groups = {}
self.commands = {}
self.__setup_hosts()
self.__setup_uinst()
self.__setup_woinst()
self.__setup_toinst()
def parse(self, predicates):
for p in predicates:
cmd = p[0]
args = p[1:]
if cmd == 'default_profile': self.handle_default_profile(args)
elif cmd == 'default_domain': self.handle_default_domain(args)
elif cmd == 'group': self.handle_group(args)
elif cmd == 'attr': self.handle_attr(args)
elif cmd == 'host': self.handle_host(*p)
elif cmd in self.commands: self.commands[cmd](*p)
return self
def register_command(self, name, method):
self.commands[name] = method
############################################################################
def reset_group(self, otype):
2017-06-18 11:57:38 +04:00
self.groups[otype]['current'] = set()
def reset_group_maybe(self, otype):
if self.groups[otype]['type'] in ('defaults', 'once'):
self.reset_group(otype)
self.groups[otype]['type'] = 'once'
def add_group(self, otype, id):
2017-06-18 11:57:38 +04:00
self.groups[otype]['current'].add(id)
def handle_group(self, args):
"""group otype gtype
gtype peut valoir:
- defaults: revenir à l'état initial, permettant de spécifier les
attributs et liens pour tous les hôtes définis à partir de cette ligne
- once: un nouveau groupe est défini à chaque nouvelle ligne 'group'
- until: définir un groupe qui va jusqu'à la prochaine ligne 'group'
"""
otype = args[0:1] and args[0] or 'host'
2017-06-15 02:24:17 +04:00
if otype not in self.groups:
raise ValueError('%s: invalid object type' % otype)
gtype = args[1:2] and args[1] or 'until'
self.groups[otype]['type'] = gtype
if gtype == 'defaults':
2017-06-18 11:57:38 +04:00
self.groups[otype]['current'] = set([None])
elif gtype in ('once', 'until'):
2017-06-18 11:57:38 +04:00
self.groups[otype]['current'] = set()
2017-06-15 02:24:17 +04:00
else:
raise ValueError('%s: invalid group type' % gtype)
self.attr_otype = otype
############################################################################
def handle_attr(self, args):
otype = self.attr_otype
assert otype is not None, "attr_otype should not be None"
for nv in args:
name, value, type, method = split_namev(nv)
if value is None:
2017-06-15 02:24:17 +04:00
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for id in self.groups[otype]['current']:
self.db.get_object(otype, id).modify_attr(name, value, method)
############################################################################
def handle_default_profile(self, args):
if not args or not args[0]: profile = None
else: profile = args[0]
self.db.default_profile = profile
############################################################################
def handle_default_domain(self, args):
if not args or not args[0]: domain = None
else: domain = args[0]
self.db.default_domain = domain
############################################################################
def __setup_hosts(self):
self.db.register_object('host', HostObject)
self.db.get_object('host', None)
self.groups['host'] = {}
self.handle_group(['host', 'defaults'])
def __fix_host(self, host):
if host.endswith('.'):
host = host[:-1]
elif '.' not in host and self.db.has_default_domain():
host = '%s.%s' % (host, self.db.default_domain)
return host
def handle_host(self, otype, *args):
"""host [id=]host,...
host -b [id=]basedir
host -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
# construire la liste des hôtes à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les hôtes
for nvs in nvss:
name, values = split_nlist(nvs)
2017-06-18 08:53:34 +04:00
for value in values:
2017-06-15 02:24:17 +04:00
dir = None
2017-06-18 08:53:34 +04:00
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_host(value)
if name:
o = self.db.get_object(otype, name)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, name)
else:
2017-06-18 08:53:34 +04:00
id = HostObject.genid(value)
o = self.db.get_object(otype, id)
2017-06-18 08:53:34 +04:00
o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, id)
ib = HostObject.genib(id)
o = self.db.get_object(otype, ib)
2017-06-18 08:53:34 +04:00
o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
self.add_group(otype, ib)
############################################################################
def __setup_uinst(self):
self.db.register_object('module', ModuleObject)
2017-06-16 00:36:36 +04:00
self.db.register_link('uinst', UinstLink)
self.db.register_link('ruinst', RuinstLink)
self.db.get_object('module', None)
self.groups['module'] = {}
self.handle_group(['module', 'defaults'])
self.register_command('module', self.handle_module)
self.register_command('uinst', self.handle_xuinst)
self.register_command('ruinst', self.handle_xuinst)
def __fix_module(self, module):
return module
def handle_module(self, otype, *args):
"""module [id=]path/to/module
module -b [id=]basedir
module -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
# construire la liste des modules à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les modules
for nvs in nvss:
name, values = split_nlist(nvs)
2017-06-18 08:53:34 +04:00
for value in values:
dir = None
2017-06-18 08:53:34 +04:00
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_module(value)
if dir is not None:
2017-06-18 08:53:34 +04:00
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
2017-06-18 08:53:34 +04:00
id = ModuleObject.genid(value)
o = self.db.get_object(otype, id)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
2017-06-16 00:36:36 +04:00
def handle_xuinst(self, ltype, *args):
"""uinst -p profile attrs*
ruinst -p profile attrs*
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
profiles = o.profiles or [None]
2017-06-18 21:59:38 +04:00
#XXX spliter profiles sur ','
2017-06-16 00:36:36 +04:00
for profile in profiles:
# préparer la mise à jour du groupe courant
2017-06-18 11:57:38 +04:00
currentls = self.db.get_links(ltype, profile, fo=self.groups['module']['current'], create=True)
2017-06-16 00:36:36 +04:00
if profile is not None:
2017-06-18 11:57:38 +04:00
globalls = self.db.get_links(ltype, None, fo=None, create=True)
2017-06-16 00:36:36 +04:00
for currentl in currentls:
2017-06-18 11:57:38 +04:00
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
2017-06-16 00:36:36 +04:00
for currentl in currentls:
2017-06-18 11:57:38 +04:00
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
2017-06-16 00:36:36 +04:00
# traiter les liens
2017-06-18 21:59:38 +04:00
for nvs in o.nvss:
2017-06-18 11:57:38 +04:00
name, value, type, method = split_namev(nvs)
2017-06-16 00:36:36 +04:00
if name == defaultl.ttype:
# définir des destinations du lien
2017-06-18 11:57:38 +04:00
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
2017-06-16 00:36:36 +04:00
else:
# définir un attribut du lien
2017-06-18 11:57:38 +04:00
#name, value, type, method = split_namev(nv)
2017-06-16 00:36:36 +04:00
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
2017-06-18 11:57:38 +04:00
for currentl in currentls:
currentl.modify_attr(name, value, method)
############################################################################
def __setup_woinst(self):
self.db.register_object('wobundle', WobundleObject)
2017-06-18 21:59:38 +04:00
self.db.register_link('rwoinst-bundle', RwoinstBundleLink)
self.db.register_link('rwoinst-webres', RwoinstWebresLink)
self.db.get_object('wobundle', None)
self.groups['wobundle'] = {}
self.handle_group(['wobundle', 'defaults'])
self.register_command('wobundle', self.handle_wobundle)
self.register_command('rwoinst', self.handle_rwoinst)
def __fix_wobundle(self, wobundle):
if path.splitext(wobundle)[1] not in ('.woa', '.framework'):
wobundle = wobundle + '.woa'
return wobundle
def handle_wobundle(self, otype, *args):
"""wobundle [id=]path/to/wobundle
wobundle -b [id=]basedir
wobundle -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
# construire la liste des wobundles à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name)) and path.splitext(name)[1] in ('.woa', '.framework')])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir) and path.splitext(dir)[1] in ('.woa', '.framework')])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les wobundles
for nvs in nvss:
name, values = split_nlist(nvs)
2017-06-18 08:53:34 +04:00
for value in values:
dir = None
2017-06-18 08:53:34 +04:00
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_wobundle(value)
if dir is not None:
2017-06-18 08:53:34 +04:00
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
2017-06-18 08:53:34 +04:00
id = WobundleObject.genid(value)
o = self.db.get_object(otype, id)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
2017-06-18 21:59:38 +04:00
def handle_rwoinst(self, ltype, *args):
"""rwoinst bundle -p profile attrs*
rwoinst webres -p profile attrs*
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('stype', nargs=1)
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
ltype = "%s-%s" % (ltype, o.stype[0])
profiles = o.profiles or [None]
#XXX spliter profiles sur ','
for profile in profiles:
# préparer la mise à jour du groupe courant
currentls = self.db.get_links(ltype, profile, fo=self.groups['wobundle']['current'], create=True)
if profile is not None:
globalls = self.db.get_links(ltype, None, fo=None, create=True)
for currentl in currentls:
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
for currentl in currentls:
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
# traiter les liens
for nvs in o.nvss:
name, value, type, method = split_namev(nvs)
if name == defaultl.ttype:
# définir des destinations du lien
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
else:
# définir un attribut du lien
#name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for currentl in currentls:
currentl.modify_attr(name, value, method)
############################################################################
def __setup_toinst(self):
self.db.register_object('webapp', WebappObject)
2017-06-18 21:59:38 +04:00
self.db.register_link('rtoinst', RtoinstLink)
self.db.get_object('webapp', None)
self.groups['webapp'] = {}
self.handle_group(['webapp', 'defaults'])
self.register_command('webapp', self.handle_webapp)
self.register_command('rtoinst', self.handle_rtoinst)
def __fix_webapp(self, webapp):
return webapp
def handle_webapp(self, otype, *args):
"""webapp [id=]path/to/webapp
webapp -b [id=]basedir
webapp -d [id=]dirspec
"""
AP = ArgumentParser()
AP.add_argument('-b', '--basedir', action='append', dest='basedirs')
AP.add_argument('-d', '--dirspec', action='append', dest='dirspecs')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
# construire la liste des webapps à traiter
nvss = []
if o.basedirs is not None:
for pb in o.basedirs:
p, b = split_nvalue(pb)
if p is None: prefix = ''
else: prefix = p + '='
basedir = path.expanduser(b)
nvss.extend([prefix + path.abspath(path.join(basedir, name))
for name in os.listdir(basedir)
if path.isdir(path.join(basedir, name))])
if o.dirspecs is not None:
for pd in o.dirspecs:
p, d = split_nvalue(pd)
if p is None: prefix = ''
else: prefix = p + '='
dirspec = path.expanduser(d)
nvss.extend([prefix + path.abspath(dir)
for dir in glob(dirspec)
if path.isdir(dir)])
nvss.extend(o.nvss)
# préparer la mise à jour du groupe courant
self.reset_group_maybe(otype)
self.attr_otype = otype
defaulto = self.db.get_object(otype, None)
# traiter les webapps
for nvs in nvss:
name, values = split_nlist(nvs)
2017-06-18 08:53:34 +04:00
for value in values:
dir = None
2017-06-18 08:53:34 +04:00
valuep = None
if value:
if '/' in value:
dir, value = path.split(path.abspath(path.expanduser(value)))
value = self.__fix_webapp(value)
if dir is not None:
2017-06-18 08:53:34 +04:00
valuep = path.join(dir, value)
if name:
o = self.db.get_object(otype, name)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, name)
else:
2017-06-18 08:53:34 +04:00
id = WebappObject.genid(value)
o = self.db.get_object(otype, id)
2017-06-18 08:53:34 +04:00
if value: o.add_value(value)
2017-06-16 00:36:36 +04:00
o.merge_attrs(defaulto)
if dir is not None: o.set_attr('dir', dir)
2017-06-18 08:53:34 +04:00
if valuep is not None: o.set_attr('path', valuep)
self.add_group(otype, id)
2017-06-18 21:59:38 +04:00
def handle_rtoinst(self, ltype, *args):
"""rtoinst -p profile attrs*
"""
AP = ArgumentParser()
AP.add_argument('-p', '--profile', action='append', dest='profiles')
AP.add_argument('nvss', nargs=REMAINDER)
o = AP.parse_args(args)
profiles = o.profiles or [None]
#XXX spliter profiles sur ','
for profile in profiles:
# préparer la mise à jour du groupe courant
currentls = self.db.get_links(ltype, profile, fo=self.groups['webapp']['current'], create=True)
if profile is not None:
globalls = self.db.get_links(ltype, None, fo=None, create=True)
for currentl in currentls:
for globall in globalls:
currentl.merge_attrs(globall)
defaultls = self.db.get_links(ltype, profile, fo=None, create=True)
for currentl in currentls:
for defaultl in defaultls:
currentl.merge_attrs(defaultl)
defaultl = defaultls[0]
# traiter les liens
for nvs in o.nvss:
name, value, type, method = split_namev(nvs)
if name == defaultl.ttype:
# définir des destinations du lien
if value is not None:
tos = split_nlist(nvs)[1]
for to in tos:
for currentl in currentls:
currentl.add_to(to)
else:
# définir un attribut du lien
#name, value, type, method = split_namev(nv)
if value is None:
method = 'set'
value = '1'
elif type == 'path':
value = path.expanduser(value)
for currentl in currentls:
currentl.modify_attr(name, value, method)
################################################################################
# Programme principal
def run_qdd(o):
# fichier de configuration
confname = o.confname or 'deploy'
if '/' in confname:
conf = path.abspath(confname)
elif path.exists('%s.conf' % confname):
conf = '%s.conf' % confname
else:
conf = confname
if not conf.endswith('.conf'): conf = '%s.conf' % conf
user_conf = path.join(path.expanduser(USER_CONFDIR), conf)
system_conf = path.join(SYSTEM_CONFDIR, conf)
if path.exists(user_conf): conf = user_conf
elif path.exists(system_conf): conf = system_conf
lexer = Lexer(conf)
db = Database()
if o.action == 'nop':
pass
elif o.action == 'dump': #XXX
predicates = lexer.get_predicates()
parser = Parser(db).parse(predicates)
print "=== predicates"
for p in predicates:
print ' '.join(map(repr, p))
2017-06-18 21:59:38 +04:00
otypes = db.known_otypes[:]
otypes.remove('host')
otypes.insert(0, 'host')
for otype in otypes:
objects = db.get_objects(otype)
if objects:
print "=== %s" % otype
for o in objects:
o.dump()
2017-06-18 21:59:38 +04:00
for ltype in db.known_ltypes:
links = db.get_links(ltype)
if links:
print "=== %s" % ltype
for link in links:
if link.is_defaults(): continue
link.dump(" ")
elif o.action == 'query':
parser = Parser(db).parse(lexer.get_predicates())
if __name__ == '__main__':
from argparse import ArgumentParser, HelpFormatter
if sys.argv[1:2] == ['--compat']:
# Avec l'argument --compat, désactiver la classe FancyHelpFormatter qui
# se base sur une API non documentée
sys.argv = sys.argv[0:1] + sys.argv[2:]
FancyHelpFormatter = HelpFormatter
else:
class FancyHelpFormatter(HelpFormatter):
"""Comme HelpFormatter, mais ne touche pas aux lignes qui commencent par les
caractères '>>>'. Cela permet de mixer du texte formaté et du texte non
formaté.
"""
def _fill_text(self, text, width, indent):
return ''.join([indent + line for line in text.splitlines(True)])
def _split_lines(self, text, width):
lines = ['']
for line in text.splitlines():
if line.startswith('>>>'):
lines.append(line)
lines.append('')
else:
lines[-1] += '\n' + line
lines = filter(None, lines)
texts = []
for line in lines:
if line.startswith('>>>'):
line = line[3:]
if line: texts.append(line)
else:
texts.extend(super(FancyHelpFormatter, self)._split_lines(line, width))
return texts
AP = ArgumentParser(
usage=u"%(prog)s --query FILTER",
description=__doc__,
formatter_class=FancyHelpFormatter,
)
AP.set_defaults(action='query')
AP.add_argument('-Q', '--query-action', action='store_const', dest='action', const='query',
help=u"Interroger la base de données. C'est l'option par défaut")
AP.add_argument('-N', '--nop-action', action='store_const', dest='action', const='nop',
help=u"Ne rien faire. Utile pour vérifier si le fichier ne contient pas d'erreur de syntaxe.")
AP.add_argument('-P', '--dump-action', action='store_const', dest='action', const='dump',
help=u"Afficher le contenu de la base de données.")
AP.add_argument('-c', '--config', dest='confname',
help=u"Spécifier le nom de la configuration à utiliser. Par défaut, utiliser le nom générique deploy.")
AP.add_argument('-q', '--query-type', dest='query_type',
help=u"")
AP.add_argument('-j', '--object-type', dest='object_type',
help=u"")
AP.add_argument('-t', '--link-type', dest='link_type',
help=u"")
AP.add_argument('-p', '--profile', dest='profile',
help=u"Spécifier le profil de déploiement")
AP.add_argument('-F', '--format', dest='format',
help=u"Spécifier le format pour la sortie. La valeur par défaut est shell.")
AP.add_argument('-v', '--include-vars', dest='vars', metavar='VARS...',
help=u"Spécifier les variables qui doivent être affichées. Par défaut, toutes les variables sont affichées.")
AP.add_argument('-o', '--object-vars', action='store_true', dest='object_vars',
help=u"Afficher les variables associées aux objets.")
AP.add_argument('-d', '--dest-vars', action='store_true', dest='dest_vars',
help=u"Afficher les variables associées aux hôtes destination.")
AP.add_argument('-l', '--link-vars', action='store_true', dest='link_vars',
help=u"Afficher les variables associées aux liens.")
AP.add_argument('--of', '--object-func', dest='object_func', metavar='FUNC',
help=u"Avec le format shell, spécifier le nom d'une fonction à afficher après les variables associées aux objets.")
AP.add_argument('--df', '--dest-func', dest='dest_func', metavar='FUNC',
help=u"Avec le format shell, spécifier le nom d'une fonction à afficher après les variables associées aux hôtes destination.")
AP.add_argument('--lf', '--link-func', dest='link_func', metavar='FUNC',
help=u"Avec le format shell, spécifier le nom d'une fonction à afficher après les variables associées aux liens.")
o = AP.parse_args()
run_qdd(o)