recherche d'objets

This commit is contained in:
Jephté Clain 2017-07-18 20:04:02 +04:00
parent 38fdb21f91
commit c8e13163d3
1 changed files with 230 additions and 40 deletions

View File

@ -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)