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

240 lines
7.9 KiB
Python

# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
__all__ = (
'Deploydb',
'Host',
'host_matcher', 'hostname_matcher',
)
import logging; log = logging.getLogger(__name__)
import os, sys, socket, csv
from os import path
from .utils import *
from .objects import XT, fileP, pathP, lowerP, Object, catalog
################################################################################
# Configuration de deploydb
class Deploydb(Object):
ATTRS = XT(Object, dir=pathP, file=fileP)
################################################################################
# Gestion des groupes
class Group(Object):
"""Groupe d'objets liés.
Lors du resolve, toutes les variables définies pour le groupe sont propagées
aux objets liés si elles ne sont pas définies dans ces objets.
dans l'exemple suivant:
~~~
group mymodules shared=all
-module module1,module2
moduledir=~/wop/php
-host host1,host2
domain=long.tld
~~~
la variable shared est initialisée pour module1,module2,host1,host2 alors que
la variable moduledir ne concerne que module1,module2 et la variable domain ne
concerne que host1,host2
"""
__RESOLVE_FIRST__ = True
ATTRS = XT(Object)
def _resolve(self, catalog):
for otype, links in self.links.items():
for link in links:
object = link.resolve(catalog, resolve=False)
object.set_defaults(link.attrs)
object.set_defaults(self.attrs)
################################################################################
# Gestion des hôtes
def withdomain(h): return '.' in h
def fix_host(host, domain=None):
if host.endswith('.'):
host = host[:-1]
elif domain and not withdomain(host):
host = "%s.%s" % (host, domain)
return host
def strip_hostname(a):
pos = a.find('.')
if pos == -1: return None
else: return a[pos + 1:]
def strip_domain(a):
pos = a.find('.')
if pos == -1: return a
else: return a[:pos]
def match_host(qhost, object):
qhost = lowerP.parse(qhost)
if withdomain(qhost): # host avec chemin
qhost = fix_host(qhost)
return qhost in object.get('host', ())
else: # nom de host
return qhost in object.get('hostname', ())
def host_matcher(qhost):
return lambda object: match_host(qhost, object)
def match_hostname(qhost, object):
qhost = lowerP.parse(qhost)
qhost = path.basename(qhost)
return qhost in object.get('hostname', ())
def hostname_matcher(qhost):
return lambda object: match_hostname(qhost, object)
class Host(Object):
ATTRS = XT(Object,
values=lowerP,
host=lowerP, hostname=lowerP, domain=lowerP, ip=None)
def _resolve(self, catalog):
if self.oid == '*': return
default = catalog.get(self.otype, '*', None, False)
hosts = self.get('host', [])
hostnames = self.get('hostname', ())
domains = self.get('domain', ())
search_basedir = self.get('search_basedir', ('dirs',))
files = 'files' in search_basedir
dirs = 'dirs' in search_basedir
basedir = self.get('basedir', None)
if basedir is not None:
hostdirs = self.resolve_basedir(basedir, files=files, dirs=dirs)
hosts.extend(map(path.basename, hostdirs))
dirspec = self.get('dirspec', None)
if dirspec is not None:
hostdirs = self.resolve_filespec(dirspec, dirs=True)
hosts.extend(map(path.basename, hostdirs))
filespec = self.get('filespec', None)
if filespec is not None:
hostfiles = self.resolve_filespec(filespec, files=True)
hosts.extend(map(path.basename, hostfiles))
if hosts:
# générer hostname et domain à partir host
if not domains:
domains = set(map(strip_hostname, hosts))
domains = filter(lambda d: d is not None, domains)
if not domains and default is not None:
domains = default.get('domain', ())
domains = filter(None, domains)
if domains: domains = self.domain = set(domains)
hostnames = map(strip_domain, hostnames or hosts)
if hostnames: hostnames = self.hostname = set(hostnames)
if domains:
tmphosts = []
for host in hosts:
for domain in domains:
tmphosts.append(fix_host(host, domain))
else:
tmphosts = map(fix_host, hosts)
hosts = self.host = set(tmphosts)
else:
# générer host à partir de hostname et domain
if not domains and default is not None:
domains = default.get('domain', ())
if domains: domains = self.domain = set(domains)
if not hostnames: hostnames = [self.oid]
hostnames = map(strip_domain, hostnames)
if hostnames: self.hostname = hostnames
if domains:
hosts = []
for domain in domains:
for hostname in hostnames:
hosts.append('%s.%s' % (hostname, domain))
else:
hosts = hostnames
if hosts: hosts = self.host = set(hosts)
ips = self.get('ip', [])
if not ips:
for host in hosts:
try:
hostnames, aliases, ipaddrs = socket.gethostbyname_ex(host)
ips.extend(ipaddrs)
except socket.herror, e:
log.error("error resolving %s: %s, %s", host, e[0], e[1])
except socket.gaierror, e:
log.error("error resolving %s: %s, %s", host, e[0], e[1])
if ips: ips = self.ip = set(ips)
if not self.values:
self.values = hosts
def save_hosts(*args):
"""Ecrire les hôtes définis sous forme de liste csv, qu'il est possible
d'exploiter avec 'deploydb loadcsv'
plus ou moins équivalent à `save_objects host` mais les champs sont dans un
ordre ergonomique (cette fonction a été écrite en premier, elle est gardée
pour l'historique)
"""
# tout d'abord déterminer tous les attributs nécessaires
headers = ['host', 'hostname', 'domain', 'ip']
hosts = catalog.find_objects('host')
for host in hosts:
for name in host.attrs.keys():
if name not in headers: headers.append(name)
# ensuite les écrire
rows = []
for host in hosts:
if host.oid == '*': continue
row = [host.otype, host.oid]
for name in headers:
row.append(';'.join(host.get(name, ())))
rows.append(row)
headers[0:0] = ['otype', 'oid']
# écrire le résultat
out = csv.writer(sys.stdout)
out.writerow(headers)
out.writerows(rows)
################################################################################
# Actions
def save_objects(*args):
"""Ecrire les objets sous forme de liste csv, qu'il est possible d'exploiter
avec 'deploydb loadcsv'
usage: save_objects [otype [oids...]]
"""
otypes = listof(args[0] if args[0:1] else None, None)
if otypes is not None: otypes = flattenstr(otypes)
oids = args[1:] or None
objects = catalog.find_objects(otypes, oids, create=False)
# tout d'abord déterminer tous les attributs nécessaires
headers = ['otype', 'oid']
for object in objects:
for name in object.known_rw_attrs:
if name not in headers: headers.append(name)
for object in objects:
for name in object.misc_attrs:
if name not in headers: headers.append(name)
# ensuite les écrire
rows = []
for object in objects:
row = []
for name in headers:
row.append(';'.join(object.get(name, ())))
rows.append(row)
# écrire le résultat
out = csv.writer(sys.stdout)
out.writerow(headers)
out.writerows(rows)
def query(*args):
pass