nutools/lib/nulib/python/deploydb/toinst_module.py

378 lines
14 KiB
Python
Raw Normal View History

2018-04-26 23:19:17 +04:00
# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
__all__ = (
'Webapp',
'webapp_matcher', 'webappname_matcher',
)
import logging; log = logging.getLogger(__name__)
import os, sys
from os import path
from .utils import *
from .expr import *
from .objects import XT, fileP, pathP, mpathP, lowerP, Object, catalog
from .parser import Parser
from .base_module import withdomain, host_matcher, hostname_matcher
################################################################################
# Webapp
def match_webapp(qwebapp, object):
if withpath(qwebapp): # webapp avec chemin
return qwebapp in object.get('webapp', ())
else: # nom de webapp
return qwebapp in object.get('webappname', ())
def webapp_matcher(qwebapp):
return lambda object: match_webapp(qwebapp, object)
def match_webappname(qwebapp, object):
qwebapp = path.basename(qwebapp)
return qwebapp in object.get('webappname', ())
def webappname_matcher(qwebapp):
return lambda object: match_webappname(qwebapp, object)
class Webapp(Object):
ATTRS = XT(Object,
values=pathP, webapp=mpathP, webappdir=pathP,
basedir=pathP, dirspec=fileP)
def _resolve(self, catalog):
if self.oid == '*': return
default = catalog.get(self.otype, '*', None, False)
webapps = self.get('webapp', [])
basedir = self.get('basedir', None)
if basedir is not None:
webapps.extend(self.resolve_basedir(basedir, dirs=True))
dirspec = self.get('dirspec', None)
if dirspec is not None:
webapps.extend(self.resolve_filespec(dirspec, dirs=True))
if webapps:
# générer webappdir et webappname à partir de webapp
webappdirs = [path.dirname(webapp) for webapp in webapps]
if webappdirs: webappdirs = self.webappdir = ulistof(webappdirs)
webappnames = [path.basename(webapp) for webapp in webapps]
if webappnames: webappnames = self.webappname = ulistof(webappnames)
else:
# générer webapps à partir de webappdir et webappname
webappdirs = self.get('webappdir', ())
if not webappdirs and default is not None:
webappdirs = default.get('webappdir', ())
if webappdirs: webappdirs = self.webappdir = ulistof(webappdirs)
webappnames = self.get('webappname', ())
if not webappnames: webappnames = [self.oid]
if webappnames: webappnames = self.webappname = ulistof(webappnames)
if webappdirs:
webapps = []
for webappname in webappnames:
found = []
for webappdir in webappdirs:
webapp = path.join(webappdir, webappname)
if path.exists(webapp):
found.append(webapp)
break
if not found:
found = [path.join(webappdirs[0], webappname)]
webapps.extend(found)
else:
webapps = webappnames
if webapps: webapps = self.webapp = ulistof(webapps)
if not self.values:
self.values = webapps
################################################################################
# Actions
def option_choice(yesoption, nooption):
def func(value, *ignored):
if istrue(value): return yesoption
else: return nooption
return func
def pffprofile_support(value, fact, webapp):
pffprofile = fact.get('pffprofile', None)
if pffprofile is None: pffprofile = webapp.get('pffprofile', None)
if pffprofile is None: return None
if value == 'ensure':
return ['--ensure-pffprofile', pffprofile[0]]
elif value == 'set':
return ['--set-pffprofile', pffprofile[0]]
TOINST_ATTRS = {
'tomcat_profile': dict(
option='--config-profile',
), 'catalina_base': dict(
option='--catalina-base',
), 'tomcat_user': dict(
option='--tomcat-user',
), 'tomcat_group': dict(
option='--tomcat-group',
), 'tomcat_version': dict(
option='--tomcat-version',
), 'manager_url': dict(
option='--manager-url',
), 'manager_user': dict(
option='--manager-user',
), 'manager_password': dict(
option='--manager-password',
), 'wamap': dict(
option='--wamap',
multiple=True,
flattensep=',',
), 'exclude': dict(
option='--exclude',
multiple=True,
), 'exclude_override': dict(
option='--replace-excludes',
multiple=True,
), 'protect': dict(
option='--protect',
multiple=True,
), 'rsync_option': dict(
option='--rsync-option',
multiple=True,
), 'rsync_option_override': dict(
option='--replace-rsync-options',
multiple=True,
), 'backup': dict(
func=option_choice('--backup', '--no-backup'),
), 'restart': dict(
func=option_choice('--restart', '--no-restart'),
), 'legacy_sort': dict(
func=option_choice('--legacy-sort', None),
), 'pffprofile_support': dict(
func=pffprofile_support,
),
}
def query_rtoinst(*args):
"""afficher la commande pour déployer avec la commande $1 la webapp $2 sur
l'hôte $3 dans le profil $4 ou le profil pff $5
$1 doit valoir 'rtoinst' ou être un chemin vers ce script
$2 peut être
* un nom de webapp: toutes les webapps de ce nom sont sélectionnés
* un chemin complet: si une webapp avec le chemin complet est trouvée, ne
sélectinner que celle-, sinon faire comme si on n'avait spécifié que le
nom de la webapp
* non spécifié: toutes les webapps devant être déployé sur l'hôte sont
cherchées
$3 peut être
* un nom d'hôte: tous les hôtes de ce nom sont sélectionés
* un nom d'hôte pleinement qualifié: si le nom d'hôte pleinement qualifié
est trouvé, ne sélectionner que celui-, sinon faire comme si on n'avait
spécifié que le nom d'hôte
* non spécifié: tous les hôtes vers lequel doit être déployé le webapp sont
cherchés
$4 peut valoir
* 'NONE': seuls les déploiements sans profils définis sont sélectionnés.
c'est la valeur par défaut.
* 'ALL' ou '': ne pas tenir compte du profil lors de la sélection des
webapps et des hôtes
* toute autre valeur, e.g prod ou test: seuls les déploiements de ce profil
sont sélectionnés
Il est possible de spécifier plusieurs profils en les séparant par des
virgules. Par exemple, 'NONE,prod' permet de sélectionner les déploiements
sans profil ou dans le profil 'prod'
$5 peut valoir
* 'NONE': seuls les déploiement sans profils pff définis sont sélectionnés.
* 'ALL' ou '': ne pas tenir compte du profil pff lors de la sélection des
webapps et des hôtes. c'est la valeur par défaut.
* toute autre valeur, e.g prod ou test: seuls les déploiements de ce profil
pff sont sélectionnés.
la webapp, ou l'hôte, ou les deux sont requis. le profil et le profil pff
sont facultatifs.
Les valeurs $5..$* sont des définitions d'attributs utilisées pour mettre à
jour les faits trouvés. Les mappings suivants sont supportés:
= attribut = = option de toinst =
tomcat_profile --config-profile
catalina_base --catalina-base
tomcat_user --tomcat-user
tomcat_group --tomcat-group
tomcat_version --tomcat-version
manager_url --manager-url
manager_user --manager-user
manager_password --manager-password
wamap --wamap
exclude --exclude
exclude_override --replace-excludes
protect --protect
rsync_option --rsync-option
rsync_option_override --replace-rsync-options
backup --backup / --no-backup
restart --restart / --no-restart
legacy_sort --legacy-sort
pffprofile_support --ensure-pffprofile / --set-pffprofile
"""
rtoinst = args[0] if args[0:1] else None
if rtoinst is not None and (rtoinst == 'rtoinst' or rtoinst.endswith('/rtoinst')):
verb = 'rtoinst'
else:
raise ValueError("Le verbe est requis et doit valoir 'rtoinst'")
qwebapp = args[1:2] and args[1] or None
qhost = args[2:3] and args[2] or None
qprofile = args[3] if args[3:4] else 'NONE'
qpffprofile = args[4] if args[4:5] else 'ALL'
supplattrs = args[5:]
if not qwebapp and not qhost:
raise ValueError("Il faut spécifier webapp et/ou host")
if not qwebapp:
webapps = None
elif cwithpath(qwebapp):
qwebapp = path.abspath(qwebapp)
webapps = catalog.find_objects('webapp', expr=webapp_matcher(qwebapp))
if not webapps:
webapps = catalog.find_objects('webapp', expr=webappname_matcher(qwebapp))
else:
webapps = catalog.find_objects('webapp', expr=webappname_matcher(qwebapp))
if not qhost:
hosts = None
else:
if cwithpath(qhost):
qhost = path.basename(path.abspath(qhost))
if withdomain(qhost):
hosts = catalog.find_objects('host', expr=host_matcher(qhost))
if not hosts:
hosts = catalog.find_objects('host', expr=hostname_matcher(qhost))
else:
hosts = catalog.find_objects('host', expr=hostname_matcher(qhost))
if qprofile == '': qprofile = 'ALL'
qprofiles = flattenstr([qprofile])
if 'ALL' in qprofiles:
qprofile = None
else:
expr = []
for qprofile in qprofiles:
if qprofile == 'NONE':
qprofile = NONE(EXISTS('profile'))
else:
qprofile = dict(profile=qprofile)
expr.append(qprofile)
qprofile = ANY(*expr)
if qpffprofile == '': qpffprofile = 'ALL'
qpffprofiles = flattenstr([qpffprofile])
if 'ALL' in qpffprofiles:
qpffprofile = None
else:
expr = []
for qpffprofile in qpffprofiles:
if qpffprofile == 'NONE':
qpffprofile = NONE(EXISTS('pffprofile'))
else:
qpffprofile = dict(pffprofile=qpffprofile)
expr.append(qpffprofile)
qpffprofile = ANY(*expr)
if qprofile is None and qpffprofile is None:
expr = None
elif qprofile is not None and qpffprofile is not None:
expr = ALL(qprofile, qpffprofile)
elif qprofile is not None:
expr = qprofile
elif qpffprofile is not None:
expr = qpffprofile
# webapps et hosts sont spécifiés
if webapps is not None and hosts is not None:
facts = catalog.find_facts(
verb=verb,
tsotype='webapp', tsexpr=dict(oid=[webapp.oid for webapp in webapps]),
ttotype='host', ttexpr=dict(oid=[host.oid for host in hosts]),
expr=expr,
)
# Seuls les webapps sont spécifiés: chercher les hôtes
elif webapps is not None:
facts = catalog.find_facts(
verb=verb,
tsotype='webapp', tsexpr=dict(oid=[webapp.oid for webapp in webapps]),
ttotype='host',
expr=expr,
)
# Seuls les hôtes sont spécifiés: chercher les webapps
elif hosts is not None:
facts = catalog.find_facts(
verb=verb,
tsotype='webapp',
ttotype='host', ttexpr=dict(oid=[host.oid for host in hosts]),
expr=expr,
)
# afficher la commande
if supplattrs: parser = Parser()
for fact, tsobjects, ttobjects in facts:
if supplattrs: parser.parse_attrs(supplattrs, fact)
hs = ':'.join(flattenseq([host.host for host in ttobjects]))
for webapp in tsobjects:
# construire les options de toinst. on prend les valeurs d'abord dans le
# fait puis dans l'objet webapp.
options = []
names = set(fact.attrs.keys())
names.update(webapp.attrs.keys())
for name in names:
values = fact.get(name, None)
factvalue = True
if values is None:
values = webapp.get(name, None)
factvalue = False
if values is None:
# ne devrait pas se produire en principe
continue
if name in ('profile', 'pffprofile'):
# les attributs de sélection du profil ont été déjà été traités
# plus haut
continue
params = TOINST_ATTRS.get(name, None)
if params is None:
if factvalue:
log.warning("ignoring %s option %s=%r", fact.verb, name, values)
else:
func = params.get('func', None)
option = params.get('option', None)
if func is not None:
option = func(values[0], fact, webapp)
if option is not None:
options.extend(listof(option))
elif option is not None:
if params.get('multiple', False):
flattensep = params.get('flattensep', None)
if flattensep is not None:
values = flattenstr(values, flattensep)
for value in values:
options.append(option)
options.append(qshell(value))
else:
options.append(option)
options.append(qshell(values[0]))
else:
raise ValueError("missing option key for attribute %s" % name)
for w in webapp.webapp:
# préférer si possible le chemin fourni par l'utilisateur
if withpath(qwebapp): w = qwebapp
parts = [rtoinst, '--no-deploydb', '-yh', qshell(hs), qshell(w)]
if options:
parts.append('--')
parts.extend(options)
print ' '.join(parts)