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.
"""
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
return [objects[id] for id in objects if id is not 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)