240 lines
7.9 KiB
Python
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
|