378 lines
14 KiB
Python
378 lines
14 KiB
Python
|
# -*- 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-là, 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-là, 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)
|