From c8e13163d3b53cbe2e2bd736d104d0f703f902d9 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 18 Jul 2017 20:04:02 +0400 Subject: [PATCH] recherche d'objets --- lib/ulib/support/deploydb.py | 270 +++++++++++++++++++++++++++++------ 1 file changed, 230 insertions(+), 40 deletions(-) diff --git a/lib/ulib/support/deploydb.py b/lib/ulib/support/deploydb.py index 48d2b53..07bed61 100755 --- a/lib/ulib/support/deploydb.py +++ b/lib/ulib/support/deploydb.py @@ -13,6 +13,8 @@ SYSTEM_CONFDIR = '/var/local/deploy' # Diverses fonctions def isseq(t): + """Tester si t est une séquence + """ return isinstance(t, list) or isinstance(t, tuple) or isinstance(t, set) def flatten(src, unique=True, clean=True, sep=','): @@ -23,7 +25,6 @@ def flatten(src, unique=True, clean=True, sep=','): Si clean==True, supprimer les valeurs vides et les espaces périphériques e.g flatten(['a , b', 'c,']) --> ['a', 'b', 'c'] - """ if not isseq(src): src = [src] dest = [] @@ -37,6 +38,31 @@ def flatten(src, unique=True, clean=True, sep=','): dest.extend(items) return dest +def qshell(values): + if isseq(values): return map(qshell, values) + elif not values: return '' + else: return "'%s'" % values.replace("'", "'\\''") + +def strip_domain(a): + if a.find('.') == -1: return a + else: return a[:a.find('.')] + +def match_host(a, b): + """Les hôtes sont matchés de façon particulière: + NAME matche NAME et NAME.DOMAIN + NAME. matche NAME + NAME.DOMAIN matche NAME.DOMAIN + """ + a = a.lower() + b = b.lower() + if a == b: return True + if a.find('.') == -1: # NAME + return a == strip_domain(b) + elif a.endswith('.'): # NAME. + return a[:-1] == b + else: # NAME.DOMAIN + return a == b + ################################################################################ # Base de données @@ -47,6 +73,10 @@ class GenericObject(object): values) et des attributs (propriété attrs) """ + OTYPE = 'object' + VALUESNAME = 'values' + ATTRPREFIX = 'object_' + _id = None _values = None _attrs = None @@ -59,7 +89,12 @@ class GenericObject(object): def get_id(self): return self._id id = property(get_id) - def get_values(self): return self._values + def is_defaults(self): + """Cet objet contient-il les attributs par défaut pour le type? + """ + return self._id is None + + def get_values(self): return tuple(self._values) values = property(get_values) def add_value(self, value): self._values.add(value) @@ -110,6 +145,46 @@ class GenericObject(object): for value in values: self.modify_attr(name, value, method) + def invalid_match(self, match): + return ValueError("%s: invalid match type") + def match_values(self, values, match='any', reverse=False): + """Tester si cet objet a l'une des valeurs spécifiées (avec match=='any' qui est + la valeur par défaut) + """ + if not isseq(values): values = [values] + if reverse: Yes, No = False, True + else: Yes, No = True, False + if match == 'any': + for value in values: + if value in self._values: return Yes + return No + elif match == 'all': + for value in values: + if value not in self._values: return No + return Yes + else: + raise self.invalid_match(match) + def match_attrs(self, attrs, match='any', reverse=False): + """Tester si cet objet a un des attributs correspondant aux valeurs spécifiées + (avec match=='any' qui est la valeur par défaut) + """ + if reverse: Yes, No = False, True + else: Yes, No = True, False + if match == 'any': + for name, value in attrs.items(): + values = self._attrs.get(name, None) + if values is None: continue + if value in values: return Yes + return No + elif match == 'all': + for name, value in attrs.items(): + values = self._attrs.get(name, None) + if values is None: return No + if value not in values: return No + return Yes + else: + raise self.invalid_match(match) + def _dump_id(self, indent): print "%s%s" % (indent, self.id) def _dump_values(self, indent, name): @@ -126,13 +201,17 @@ class GenericObject(object): """Afficher l'identifiant, les valeurs et les attributs de cet objet """ self._dump_id(indent) - self._dump_values(indent, 'values') + self._dump_values(indent, self.VALUESNAME) self._dump_attrs(indent) class HostObject(GenericObject): """Un hôte """ + OTYPE = 'host' + VALUESNAME = 'hosts' + ATTRPREFIX = 'host_' + @staticmethod def genid(host): """Générer un identifiant à partir du nom d'hôte @@ -151,15 +230,31 @@ class HostObject(GenericObject): 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) + def match_values(self, values, match='any', reverse=False): + if not isseq(values): values = [values] + if reverse: Yes, No = False, True + else: Yes, No = True, False + if match == 'any': + for value in values: + for host in self._values: + if match_host(value, host): return Yes + return No + elif match == 'all': + for value in values: + for host in self._values: + if not match_host(value, host): return No + return Yes + else: + raise self.invalid_match(match) class ModuleObject(GenericObject): """Un module, uinst-allable """ + OTYPE = 'module' + VALUESNAME = 'modules' + ATTRPREFIX = 'module_' + @staticmethod def genid(module): """Générér un identifiant à partir du nom du module @@ -168,15 +263,14 @@ class ModuleObject(GenericObject): 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): """Un bundle Webobjects, woinst-allable """ + OTYPE = 'wobundle' + VALUESNAME = 'wobundles' + ATTRPREFIX = 'wobundle_' + @staticmethod def genid(wobundle): """Générér un identifiant à partir du nom du bundle @@ -186,15 +280,14 @@ class WobundleObject(GenericObject): 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): """Une webapp, toinst-allable """ + OTYPE = 'webapp' + VALUESNAME = 'webapps' + ATTRPREFIX = 'webapp_' + @staticmethod def genid(webapp): """Générér un identifiant à partir du nom de la webapp @@ -203,11 +296,6 @@ class WebappObject(GenericObject): 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) - class GenericLink(object): """Un lien générique @@ -552,13 +640,23 @@ class Database(object): """ return self._objects_ids_otype.keys() known_otypes = property(get_known_otypes) - def get_objects(self, otype): + def get_objects(self, otype, value=UNDEF, attrs=UNDEF): """Obtenir tous les objets définis du type spécifié ou None si le type d'objets - est invalide. + est invalide. Si value ou attrs sont définis, filtrer sur ces valeurs. """ - 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] + if otype is None: + objects = [] + for objects_ids in self._objects_ids_otype.values(): + objects.extend(objects_ids.values()) + else: + objects = self._objects_ids_otype.get(otype, None) + if objects is None: return None + objects = [objects[id] for id in objects if id is not None] + return [ + object for object in objects if + (value is UNDEF or object.match_values(value)) and + (attrs is UNDEF or object.match_attrs(attrs)) + ] def has_object(self, otype, id): """Vérifier si l'objet avec l'identifiant spécifié est défini """ @@ -1390,6 +1488,69 @@ class Parser(object): ################################################################################ # Programme principal +VALID_QUERY_TYPES = ('object', 'source', 'dest', 'link') +def __fix_query_type(query_type): + if query_type is None: return None + query_type = query_type.lower() + if query_type == 'o': query_type = 'object' + elif query_type == 's': query_type = 'source' + elif query_type == 'd': query_type = 'dest' + elif query_type == 'l': query_type = 'link' + if query_type not in VALID_QUERY_TYPES: + raise ValuerError("%s: invalid query type") + return query_type + +def __resolve_format(o, query_type): + format = o.format or 'shell' + ovars, lvars = o.object_vars, o.link_vars + if not ovars and not lvars: + if query_type == 'object': + ovars = True + lvars = False + elif query_type in ('source', 'dest'): + ovars = True + lvars = True + elif query_type == 'link': + ovars = False + lvars = True + ofunc, lfunc = o.object_func, o.link_func + ivars = flatten(o.vars) or None + return format, ovars, lvars, ofunc, lfunc, ivars + +def print_var(name, values, format, ivars, is_values=False): + if ivars is not None and name not in ivars: return + if not isseq(values): values = [values] + if format == 'shell': + params = (name, " ".join(qshell(values))) + if len(values) <= 1: print "%s=%s" % params + else: print "%s=(%s)" % params + return + if not is_values: return + if format == 'lines': + for value in values: + print value + elif format == 'spaces': + print " ".join(values) + elif format == 'path': + print ":".join(values) + elif format == 'comma': + print ",".join(values) + else: + raise ValueError("%s: invalid format" % format) + +def print_objects(objects, query_type, o): + if objects is None: return + format, ovars, lvars, ofunc, lfunc, ivars = __resolve_format(o, query_type) + if not ovars: return + for o in objects: + if o.is_defaults(): continue + print_var('otype', o.OTYPE, format, ivars) + print_var(o.VALUESNAME, o.values, format, ivars, True) + for name, values in o.attrs.items(): + print_var('%s%s' % (o.ATTRPREFIX, name), values, format, ivars) + if ofunc and format == 'shell': + print ofunc + def run_qdd(o): # fichier de configuration confname = o.confname or 'deploy' @@ -1409,7 +1570,7 @@ def run_qdd(o): if o.action == 'nop': pass - elif o.action == 'dump': #XXX + elif o.action == 'dump': predicates = lexer.get_predicates() parser = Parser(db).parse(predicates) print "=== predicates" @@ -1434,6 +1595,22 @@ def run_qdd(o): elif o.action == 'query': parser = Parser(db).parse(lexer.get_predicates()) + query_type, object_type, link_type = __fix_query_type(o.query_type), o.object_type, o.link_type + if query_type is None and object_type is None and link_type is None: + query_type = 'dest' + object_type = 'host' + if o.profile is not None: profile = o.profile or None + else: profile = UNDEF + args = o.args or UNDEF + if query_type == 'object': + objects = db.get_objects(object_type, args) + print_objects(objects, query_type, o) + elif query_type == 'source': + pass + elif query_type == 'dest': + pass + elif query_type == 'link': + pass if __name__ == '__main__': from argparse import ArgumentParser, HelpFormatter @@ -1482,29 +1659,42 @@ if __name__ == '__main__': 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"") + help=u"Spécifier le type d'information à afficher:\n" + + u">>>\n" + + u"* object pour des informations sur les objets,\n" + + u">>>\n" + + u"* source pour des informations sur les objets sources d'un lien,\n" + + u">>>\n" + + u"* dest pour des informations sur les objets destinations d'un lien,\n" + + u">>>\n" + + u"* link pour des informations sur un lien.\n" + + u">>>\n" + + u"La valeur par défaut est '-q dest -j host'") AP.add_argument('-j', '--object-type', dest='object_type', - help=u"") + help=u"Spécifier le type d'objet à chercher pour -q object|source|dest") AP.add_argument('-t', '--link-type', dest='link_type', - help=u"") + help=u"Spécifier le type de lien à chercher pour -q source|dest|link") AP.add_argument('-p', '--profile', dest='profile', - help=u"Spécifier le profil de déploiement") + help=u"Spécifier le profil de déploiement pour -q source|dest|link. Utiliser une valeur vide pour ne sélectionner que les liens sans profils. Ne pas spécifier l'option pour afficher tous les profils définis.") 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 le format pour la sortie. La valeur par défaut est shell.\n" + + u">>>\n" + + u"* shell affiche les définitions de variables\n" + + u">>>\n" + + u"* lines affiches les valeurs à raison d'une par lignes. les attributs ne sont pas affichés.\n" + + u">>>\n" + + u"* spaces, path et comma affichent les valeurs séparées respectivement par un espace ' ', deux point ':' et une virgule ','. les attributs ne sont pas affichés.\n") + AP.add_argument('-v', '--include-vars', action='append', 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.") + help=u"Afficher uniquement les variables associées aux objets.") AP.add_argument('-l', '--link-vars', action='store_true', dest='link_vars', - help=u"Afficher les variables associées aux liens.") + help=u"Afficher uniquement 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.") + AP.add_argument('args', nargs=REMAINDER) o = AP.parse_args() run_qdd(o)