Vieux plugin Python aux archives

This commit is contained in:
Pierre-Elliott Bécue 2015-05-23 16:11:34 +02:00
parent 3fb338e937
commit 1df9d480af
8 changed files with 0 additions and 0 deletions

View file

@ -1,683 +0,0 @@
"""All Python Type client support for Bcfg2."""
__revision__ = '$Revision$'
import binascii
from datetime import datetime
import difflib
import errno
import grp
import logging
import os
import pwd
import shutil
import stat
import sys
import time
# py3k compatibility
if sys.hexversion >= 0x03000000:
unicode = str
import Bcfg2.Client.Tools
import Bcfg2.Options
from Bcfg2.Client import XML
log = logging.getLogger('python')
# map between dev_type attribute and stat constants
device_map = {'block': stat.S_IFBLK,
'char': stat.S_IFCHR,
'fifo': stat.S_IFIFO}
def calcPerms(initial, perms):
"""This compares ondisk permissions with specified ones."""
pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
{1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
{1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
{1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
tempperms = initial
if len(perms) == 3:
perms = '0%s' % (perms)
pdigits = [int(perms[digit]) for digit in range(4)]
for index in range(4):
for (num, perm) in list(pdisp[index].items()):
if pdigits[index] & num:
tempperms |= perm
return tempperms
def normGid(entry):
"""
This takes a group name or gid and
returns the corresponding gid or False.
"""
try:
try:
return int(entry.get('group'))
except:
return int(grp.getgrnam(entry.get('group'))[2])
except (OSError, KeyError):
log.error('GID normalization failed for %s. Does group %s exist?'
% (entry.get('name'), entry.get('group')))
return False
def normUid(entry):
"""
This takes a user name or uid and
returns the corresponding uid or False.
"""
try:
try:
return int(entry.get('owner'))
except:
return int(pwd.getpwnam(entry.get('owner'))[2])
except (OSError, KeyError):
log.error('UID normalization failed for %s. Does owner %s exist?'
% (entry.get('name'), entry.get('owner')))
return False
def isString(strng, encoding):
"""
Returns true if the string contains no ASCII control characters
and can be decoded from the specified encoding.
"""
for char in strng:
if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
return False
try:
strng.decode(encoding)
return True
except:
return False
class Python(Bcfg2.Client.Tools.Tool):
"""Python File support code."""
name = 'Python'
__handles__ = [('Python', 'file'),
('Python', None)]
__req__ = {'Python': ['name']}
# grab paranoid options from /etc/bcfg2.conf
opts = {'ppath': Bcfg2.Options.PARANOID_PATH,
'max_copies': Bcfg2.Options.PARANOID_MAX_COPIES}
setup = Bcfg2.Options.OptionParser(opts)
setup.parse([])
ppath = setup['ppath']
max_copies = setup['max_copies']
def canInstall(self, entry):
"""Check if entry is complete for installation."""
if Bcfg2.Client.Tools.Tool.canInstall(self, entry):
if (entry.tag,
entry.get('type'),
entry.text,
entry.get('empty', 'false')) == ('Python',
'file',
None,
'false'):
return False
return True
else:
return False
def gatherCurrentData(self, entry):
if entry.tag == 'Python' and entry.get('type') == 'file':
try:
ondisk = os.stat(entry.get('name'))
except OSError:
entry.set('current_exists', 'false')
self.logger.debug("%s %s does not exist" %
(entry.tag, entry.get('name')))
return False
try:
entry.set('current_owner', str(ondisk[stat.ST_UID]))
entry.set('current_group', str(ondisk[stat.ST_GID]))
except (OSError, KeyError):
pass
entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
def Verifydirectory(self, entry, modlist):
"""Verify Path type='directory' entry."""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
while len(entry.get('perms', '')) < 4:
entry.set('perms', '0' + entry.get('perms', ''))
try:
ondisk = os.stat(entry.get('name'))
except OSError:
entry.set('current_exists', 'false')
self.logger.debug("%s %s does not exist" %
(entry.tag, entry.get('name')))
return False
try:
owner = str(ondisk[stat.ST_UID])
group = str(ondisk[stat.ST_GID])
except (OSError, KeyError):
self.logger.error('User/Group resolution failed for path %s' % \
entry.get('name'))
owner = 'root'
group = '0'
finfo = os.stat(entry.get('name'))
perms = oct(finfo[stat.ST_MODE])[-4:]
if entry.get('mtime', '-1') != '-1':
mtime = str(finfo[stat.ST_MTIME])
else:
mtime = '-1'
pTrue = ((owner == str(normUid(entry))) and
(group == str(normGid(entry))) and
(perms == entry.get('perms')) and
(mtime == entry.get('mtime', '-1')))
pruneTrue = True
ex_ents = []
if entry.get('prune', 'false') == 'true' \
and (entry.tag == 'Path' and entry.get('type') == 'directory'):
# check for any extra entries when prune='true' attribute is set
try:
entries = ['/'.join([entry.get('name'), ent]) \
for ent in os.listdir(entry.get('name'))]
ex_ents = [e for e in entries if e not in modlist]
if ex_ents:
pruneTrue = False
self.logger.debug("Directory %s contains extra entries:" % \
entry.get('name'))
self.logger.debug(ex_ents)
nqtext = entry.get('qtext', '') + '\n'
nqtext += "Directory %s contains extra entries:" % \
entry.get('name')
nqtext += ":".join(ex_ents)
entry.set('qtest', nqtext)
[entry.append(XML.Element('Prune', path=x)) \
for x in ex_ents]
except OSError:
ex_ents = []
pruneTrue = True
if not pTrue:
if owner != str(normUid(entry)):
entry.set('current_owner', owner)
self.logger.debug("%s %s ownership wrong" % \
(entry.tag, entry.get('name')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s owner wrong. is %s should be %s" % \
(entry.get('name'), owner, entry.get('owner'))
entry.set('qtext', nqtext)
if group != str(normGid(entry)):
entry.set('current_group', group)
self.logger.debug("%s %s group wrong" % \
(entry.tag, entry.get('name')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s group is %s should be %s" % \
(entry.get('name'), group, entry.get('group'))
entry.set('qtext', nqtext)
if perms != entry.get('perms'):
entry.set('current_perms', perms)
self.logger.debug("%s %s permissions are %s should be %s" %
(entry.tag,
entry.get('name'),
perms,
entry.get('perms')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s %s perms are %s should be %s" % \
(entry.tag,
entry.get('name'),
perms,
entry.get('perms'))
entry.set('qtext', nqtext)
if mtime != entry.get('mtime', '-1'):
entry.set('current_mtime', mtime)
self.logger.debug("%s %s mtime is %s should be %s" \
% (entry.tag, entry.get('name'), mtime,
entry.get('mtime')))
nqtext = entry.get('qtext', '') + '\n'
nqtext += "%s mtime is %s should be %s" % \
(entry.get('name'), mtime, entry.get('mtime'))
entry.set('qtext', nqtext)
if entry.get('type') != 'file':
nnqtext = entry.get('qtext')
nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'),
entry.get('name'))
entry.set('qtext', nnqtext)
return pTrue and pruneTrue
def Installdirectory(self, entry):
"""Install Path type='directory' entry."""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % \
(entry.get('name')))
return False
self.logger.info("Installing directory %s" % (entry.get('name')))
try:
fmode = os.lstat(entry.get('name'))
if not stat.S_ISDIR(fmode[stat.ST_MODE]):
self.logger.debug("Found a non-directory entry at %s" % \
(entry.get('name')))
try:
os.unlink(entry.get('name'))
exists = False
except OSError:
self.logger.info("Failed to unlink %s" % \
(entry.get('name')))
return False
else:
self.logger.debug("Found a pre-existing directory at %s" % \
(entry.get('name')))
exists = True
except OSError:
# stat failed
exists = False
if not exists:
parent = "/".join(entry.get('name').split('/')[:-1])
if parent:
try:
os.stat(parent)
except:
self.logger.debug('Creating parent path for directory %s' % (entry.get('name')))
for idx in range(len(parent.split('/')[:-1])):
current = '/'+'/'.join(parent.split('/')[1:2+idx])
try:
sloc = os.stat(current)
except OSError:
try:
os.mkdir(current)
continue
except OSError:
return False
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
try:
os.unlink(current)
os.mkdir(current)
except OSError:
return False
try:
os.mkdir(entry.get('name'))
except OSError:
self.logger.error('Failed to create directory %s' % \
(entry.get('name')))
return False
if entry.get('prune', 'false') == 'true' and entry.get("qtest"):
for pent in entry.findall('Prune'):
pname = pent.get('path')
ulfailed = False
if os.path.isdir(pname):
self.logger.info("Not removing extra directory %s, "
"please check and remove manually" % pname)
continue
try:
self.logger.debug("Unlinking file %s" % pname)
os.unlink(pname)
except OSError:
self.logger.error("Failed to unlink path %s" % pname)
ulfailed = True
if ulfailed:
return False
return self.Installpermissions(entry)
def Verifyfile(self, entry, _):
"""Verify Python type='file' entry."""
# permissions check + content check
permissionStatus = self.Verifydirectory(entry, _)
tbin = False
if entry.text == None and entry.get('empty', 'false') == 'false':
self.logger.error("Cannot verify incomplete Python type='%s' %s" %
(entry.get('type'), entry.get('name')))
return False
if entry.get('encoding', 'ascii') == 'base64':
tempdata = binascii.a2b_base64(entry.text)
tbin = True
elif entry.get('empty', 'false') == 'true':
tempdata = ''
else:
tempdata = entry.text
if type(tempdata) == unicode:
try:
tempdata = tempdata.encode(self.setup['encoding'])
except UnicodeEncodeError:
e = sys.exc_info()[1]
self.logger.error("Error encoding file %s:\n %s" % \
(entry.get('name'), e))
different = False
content = None
if not os.path.exists(entry.get("name")):
# first, see if the target file exists at all; if not,
# they're clearly different
different = True
content = ""
else:
# next, see if the size of the target file is different
# from the size of the desired content
try:
estat = os.stat(entry.get('name'))
except OSError:
err = sys.exc_info()[1]
self.logger.error("Failed to stat %s: %s" %
(err.filename, err))
return False
if len(tempdata) != estat[stat.ST_SIZE]:
different = True
else:
# finally, read in the target file and compare them
# directly. comparison could be done with a checksum,
# which might be faster for big binary files, but
# slower for everything else
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
different = content != tempdata
if different:
if self.setup['interactive']:
prompt = [entry.get('qtext', '')]
if not tbin and content is None:
# it's possible that we figured out the files are
# different without reading in the local file. if
# the supplied version of the file is not binary,
# we now have to read in the local file to figure
# out if _it_ is binary, and either include that
# fact or the diff in our prompts for -I
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
if tbin or not isString(content, self.setup['encoding']):
# don't compute diffs if the file is binary
prompt.append('Binary file, no printable diff')
else:
diff = self._diff(content, tempdata,
difflib.unified_diff,
filename=entry.get("name"))
if diff:
udiff = '\n'.join(diff)
try:
prompt.append(udiff.decode(self.setup['encoding']))
except UnicodeDecodeError:
prompt.append("Binary file, no printable diff")
else:
prompt.append("Diff took too long to compute, no "
"printable diff")
prompt.append("Install %s %s: (y/N): " % (entry.tag,
entry.get('name')))
entry.set("qtext", "\n".join(prompt))
if entry.get('sensitive', 'false').lower() != 'true':
if content is None:
# it's possible that we figured out the files are
# different without reading in the local file. we
# now have to read in the local file to figure out
# if _it_ is binary, and either include the whole
# file or the diff for reports
try:
content = open(entry.get('name')).read()
except IOError:
err = sys.exc_info()[1]
self.logger.error("Failed to read %s: %s" %
(err.filename, err))
return False
if tbin or not isString(content, self.setup['encoding']):
# don't compute diffs if the file is binary
entry.set('current_bfile', binascii.b2a_base64(content))
else:
diff = self._diff(content, tempdata, difflib.ndiff,
filename=entry.get("name"))
if diff:
entry.set("current_bdiff",
binascii.b2a_base64("\n".join(diff)))
elif not tbin and isString(content, self.setup['encoding']):
entry.set('current_bfile', binascii.b2a_base64(content))
elif permissionStatus == False and self.setup['interactive']:
prompt = [entry.get('qtext', '')]
prompt.append("Install %s %s: (y/N): " % (entry.tag,
entry.get('name')))
entry.set("qtext", "\n".join(prompt))
return permissionStatus and not different
def Installfile(self, entry):
"""Install Python type='file' entry."""
self.logger.info("Installing file %s" % (entry.get('name')))
parent = "/".join(entry.get('name').split('/')[:-1])
if parent:
try:
os.stat(parent)
except:
self.logger.debug('Creating parent path for config file %s' % \
(entry.get('name')))
current = '/'
for next in parent.split('/')[1:]:
current += next + '/'
try:
sloc = os.stat(current)
try:
if not stat.S_ISDIR(sloc[stat.ST_MODE]):
self.logger.debug('%s is not a directory; recreating' \
% (current))
os.unlink(current)
os.mkdir(current)
except OSError:
return False
except OSError:
try:
self.logger.debug("Creating non-existent path %s" % current)
os.mkdir(current)
except OSError:
return False
# If we get here, then the parent directory should exist
if (entry.get("paranoid", False) in ['true', 'True']) and \
self.setup.get("paranoid", False) and not \
(entry.get('current_exists', 'true') == 'false'):
bkupnam = entry.get('name').replace('/', '_')
# current list of backups for this file
try:
bkuplist = [f for f in os.listdir(self.ppath) if
f.startswith(bkupnam)]
except OSError:
e = sys.exc_info()[1]
self.logger.error("Failed to create backup list in %s: %s" %
(self.ppath, e.strerror))
return False
bkuplist.sort()
while len(bkuplist) >= int(self.max_copies):
# remove the oldest backup available
oldest = bkuplist.pop(0)
self.logger.info("Removing %s" % oldest)
try:
os.remove("%s/%s" % (self.ppath, oldest))
except:
self.logger.error("Failed to remove %s/%s" % \
(self.ppath, oldest))
return False
try:
# backup existing file
shutil.copy(entry.get('name'),
"%s/%s_%s" % (self.ppath, bkupnam,
datetime.isoformat(datetime.now())))
self.logger.info("Backup of %s saved to %s" %
(entry.get('name'), self.ppath))
except IOError:
e = sys.exc_info()[1]
self.logger.error("Failed to create backup file for %s" % \
(entry.get('name')))
self.logger.error(e)
return False
try:
newfile = open("%s.new"%(entry.get('name')), 'w')
if entry.get('encoding', 'ascii') == 'base64':
filedata = binascii.a2b_base64(entry.text)
elif entry.get('empty', 'false') == 'true':
filedata = ''
else:
if type(entry.text) == unicode:
filedata = entry.text.encode(self.setup['encoding'])
else:
filedata = entry.text
newfile.write(filedata)
newfile.close()
try:
os.chown(newfile.name, normUid(entry), normGid(entry))
except KeyError:
self.logger.error("Failed to chown %s to %s:%s" %
(newfile.name, entry.get('owner'),
entry.get('group')))
os.chown(newfile.name, 0, 0)
except OSError:
err = sys.exc_info()[1]
self.logger.error("Could not chown %s: %s" % (newfile.name,
err))
os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
os.rename(newfile.name, entry.get('name'))
if entry.get('mtime', '-1') != '-1':
try:
os.utime(entry.get('name'), (int(entry.get('mtime')),
int(entry.get('mtime'))))
except:
self.logger.error("File %s mtime fix failed" \
% (entry.get('name')))
return False
return True
except (OSError, IOError):
err = sys.exc_info()[1]
if err.errno == errno.EACCES:
self.logger.info("Failed to open %s for writing" % (entry.get('name')))
else:
print(err)
return False
def Verifypermissions(self, entry, _):
"""Verify Path type='permissions' entry"""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
if entry.get('recursive') in ['True', 'true']:
# verify ownership information recursively
owner = normUid(entry)
group = normGid(entry)
for root, dirs, files in os.walk(entry.get('name')):
for p in dirs + files:
path = os.path.join(root, p)
pstat = os.stat(path)
if owner != pstat.st_uid:
# owner mismatch for path
entry.set('current_owner', str(pstat.st_uid))
self.logger.debug("%s %s ownership wrong" % \
(entry.tag, path))
nqtext = entry.get('qtext', '') + '\n'
nqtext += ("Owner for path %s is incorrect. "
"Current owner is %s but should be %s\n" % \
(path, pstat.st_uid, entry.get('owner')))
nqtext += ("\nInstall %s %s: (y/N): " %
(entry.tag, entry.get('name')))
entry.set('qtext', nqtext)
return False
if group != pstat.st_gid:
# group mismatch for path
entry.set('current_group', str(pstat.st_gid))
self.logger.debug("%s %s group wrong" % \
(entry.tag, path))
nqtext = entry.get('qtext', '') + '\n'
nqtext += ("Group for path %s is incorrect. "
"Current group is %s but should be %s\n" % \
(path, pstat.st_gid, entry.get('group')))
nqtext += ("\nInstall %s %s: (y/N): " %
(entry.tag, entry.get('name')))
entry.set('qtext', nqtext)
return False
return self.Verifydirectory(entry, _)
def _diff(self, content1, content2, difffunc, filename=None):
rv = []
start = time.time()
longtime = False
for diffline in difffunc(content1.split('\n'),
content2.split('\n')):
now = time.time()
rv.append(diffline)
if now - start > 5 and not longtime:
if filename:
self.logger.info("Diff of %s taking a long time" %
filename)
else:
self.logger.info("Diff taking a long time")
longtime = True
elif now - start > 30:
if filename:
self.logger.error("Diff of %s took too long; giving up" %
filename)
else:
self.logger.error("Diff took too long; giving up")
return False
return rv
def Installpermissions(self, entry):
"""Install POSIX permissions"""
if entry.get('perms') == None or \
entry.get('owner') == None or \
entry.get('group') == None:
self.logger.error('Entry %s not completely specified. '
'Try running bcfg2-lint.' % (entry.get('name')))
return False
plist = [entry.get('name')]
if entry.get('recursive') in ['True', 'true']:
# verify ownership information recursively
owner = normUid(entry)
group = normGid(entry)
for root, dirs, files in os.walk(entry.get('name')):
for p in dirs + files:
path = os.path.join(root, p)
pstat = os.stat(path)
if owner != pstat.st_uid or group != pstat.st_gid:
# owner mismatch for path
plist.append(path)
try:
for p in plist:
os.chown(p, normUid(entry), normGid(entry))
os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
return True
except (OSError, KeyError):
self.logger.error('Permission fixup failed for %s' % \
(entry.get('name')))
return False
def InstallNone(self, entry):
return self.Installfile(entry)
def VerifyNone(self, entry, _):
return self.Verifyfile(entry, _)
def InstallPython(self, entry):
"""Dispatch install to the proper method according to type"""
ret = getattr(self, 'Install%s' % entry.get('type'))
return ret(entry)
def VerifyPython(self, entry, _):
"""Dispatch verify to the proper method according to type"""
ret = getattr(self, 'Verify%s' % entry.get('type'))
return ret(entry, _)

View file

@ -1,57 +0,0 @@
#!/usr/bin/python
import os, sys, time
import Bcfg2.Logger, Bcfg2.Server.Core, Bcfg2.Options
import Bcfg2.Server.Plugins.Metadata, Bcfg2.Server.Plugin
class searchCore(Bcfg2.Server.Core.Core):
# On définit une classe parce qu'il faut mettre quelque chose au-dessus
# de la classe Core si on veut récupérer les informations ...
# C'est nul.
def __init__(self, repo, plgs, struct, gens, passwd, svn,
encoding):
try:
Bcfg2.Server.Core.Core.__init__(self, repo, plgs, struct, gens,
passwd, svn, encoding)
except Bcfg2.Server.Core.CoreInitError, msg:
print "Core load failed because %s" % msg
raise SystemExit(1)
i = 0
while self.fam.Service() or i < 5:
i += 1
def do_search(self, searchgroup):
"""Search for clients belonging to a group"""
clients = []
clist = self.metadata.clients.keys()
clist.sort()
for client in clist:
profile = self.metadata.clients[client]
gdata = [grp for grp in self.metadata.groups[profile][1]]
if searchgroup in gdata:
clients.append(client)
return clients
if __name__ == '__main__':
# Bon ça ce sont les trucs recopiés de bcfg2-info
optinfo = {
'configfile': Bcfg2.Options.CFILE,
'help': Bcfg2.Options.HELP,
}
optinfo.update({'repo': Bcfg2.Options.SERVER_REPOSITORY,
'svn': Bcfg2.Options.SERVER_SVN,
'plugins': Bcfg2.Options.SERVER_PLUGINS,
'structures': Bcfg2.Options.SERVER_STRUCTURES,
'generators': Bcfg2.Options.SERVER_GENERATORS,
'password': Bcfg2.Options.SERVER_PASSWORD,
'event debug': Bcfg2.Options.DEBUG,
'encoding': Bcfg2.Options.ENCODING})
setup = Bcfg2.Options.OptionParser(optinfo)
setup.parse([])
loop = searchCore(setup['repo'], setup['plugins'], setup['structures'],
setup['generators'], setup['password'], '',
setup['encoding'])
# On cherche les clients
clients = loop.do_search('backup-client')
# On génère un truc qui va bien pour ces clients

View file

@ -1,66 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Génère un graphe de dépendance des groups bcfg2
Auteurs: Daniel Stan
Valentin Samir
"""
from __future__ import print_function
import xml.dom.minidom
import subprocess
GRAPH_BIN = '/usr/bin/neato'
TMP_FILE = '/tmp/graph.gv'
GROUPS_FILE = '/var/lib/bcfg2/Metadata/groups.xml'
OUTPUT_FILE = '/usr/scripts/var/doc/bcfg2/groups.svg'
out = file(TMP_FILE, 'w')
import datetime
# groups est le nœud racine
groups = xml.dom.minidom.parse(file(GROUPS_FILE,'r')).documentElement
print("""digraph G {
edge [len=4.50, ratio=fill]
""", file=out)
def childGroups(parent):
"""Récupère les groups enfants (dépendances) d'un nœud
Attention, cette fonction travaille sur (et renvoie) des nœuds xml
"""
return [ x for x in parent.childNodes if \
x.nodeType == x.ELEMENT_NODE and x.tagName == u'Group']
# Les clients (ie des serveurs)
# sont coloriés d'une autre couleur
print("""
subgraph cluster_1 {
node [style=filled];
""", file=out)
for elem in childGroups(groups):
if elem.hasAttribute('profile'):
print('"%s";' % elem.getAttribute('name'), file=out)
print("""
label = "process #2";
color=blue
}
""", file=out)
# Le reste
for elem in childGroups(groups):
print('"%s" -> {%s};' % \
( elem.getAttribute('name'),
" ".join( [ '"%s"' % x.getAttribute('name')
for x in childGroups(elem) ]),
), file=out)
print("""
label = "\\n\\nBCFG2 Groups\\nLes Nounous\\n%s";
}""" % datetime.datetime.now().strftime('%c'), file=out)
out.close()
subprocess.Popen([GRAPH_BIN, "-Tsvg", TMP_FILE, "-o", OUTPUT_FILE]).communicate()

View file

@ -1,52 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Envoie un mail avec la liste des serveurs qui ne sont pas synchro avec bcfg2.
Si appelé sans l'option ``--mail``, affiche le résultat sur stdout.
N'affiche que ceux qui datent d'aujourd'hui, hier ou avant-hier
(pour ne pas avoir les vieux serveurs qui traînent).
"""
from __future__ import print_function
import sys
import time
import datetime
import subprocess
unjour = datetime.timedelta(1)
aujourdhui = datetime.date(*time.localtime()[:3])
hier = aujourdhui - unjour
avanthier = aujourdhui - unjour*2
def get_dirty():
"""Récupère les hosts dirty récents."""
proc = subprocess.Popen(["/usr/sbin/bcfg2-reports", "-d"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # | sort
out, err = proc.communicate()
if err:
print(err, file=sys.stderr)
if proc.returncode != 0:
return (False, out)
out = [l for l in out.split("\n") if any([date.strftime("%F") in l for date in [aujourdhui, hier, avanthier]])]
out.sort()
return True, "\n".join(out)
if __name__ == "__main__":
success, hosts = get_dirty()
if not success:
print(hosts, file=sys.stderr)
exit(1)
debug = "--debug" in sys.argv
if "--mail" in sys.argv:
if hosts != "":
sys.path.append("/usr/scripts/")
import utils.sendmail
utils.sendmail.sendmail("root@crans.org", "roots@crans.org", u"Serveurs non synchronisés avec bcfg2", hosts, more_headers={"X-Mailer" : "bcfg2-reports"}, debug=debug)
elif debug:
print("Empty content, no mail sent")
else:
print(hosts, end="")

View file

@ -1,3 +0,0 @@
#!/bin/bash
find /var/lib/bcfg2 -name "*.COMPILED" -exec rm -f '{}' \;

View file

@ -1,26 +0,0 @@
# -*- mode: python; coding: utf-8 -*-
#
# DebAutoPkg.py
# -------------
# Copyright : (c) 2008, Jeremie Dimino <jeremie@dimino.org>
# Licence : BSD3
'''Plugin fournissant une régle par défault pour les paquets'''
__all__ = ["DebAutoPkg"]
import Bcfg2.Server.Plugin
class DebAutoPkg(Bcfg2.Server.Plugin.Plugin,Bcfg2.Server.Plugin.Generator):
name = 'DebAutoPkg'
__version__ = '1.0'
__author__ = 'dimino@crans.org'
def HandlesEntry(self, entry, metadata):
# Ce plugin fournit une règle pour toute entrée de type
# "Package"
return entry.tag == 'Package'
def HandleEntry(self, entry, metadata):
entry.attrib['version'] = 'auto'
entry.attrib['type'] = 'deb'

View file

@ -1,222 +0,0 @@
# -*- mode: python; coding: utf-8 -*-
#
# Python.py
# ---------
#
# Copyright (C) 2007 Jeremie Dimino <jeremie@dimino.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
''' Plugin pour bcfg2 pour générer des fichiers de conf en prenant la sortie d'un
script python'''
__all__ = ["Python"]
import logging, lxml.etree, posixpath, re, os, sys, binascii
from cStringIO import StringIO
import Bcfg2.Server.Plugin
import traceback
sys.path.append('/usr/scripts/bcfg2')
import pygen
logger = logging.getLogger('Bcfg2.Plugins.Python')
# Helper pour le deboggage
if os.getenv("BCFG2_DEBUG"):
debug_colored = os.getenv("BCFG2_DEBUG_COLOR")
color_code = {'grey': 30,
'red': 31,
'green': 32,
'yellow': 33,
'blue': 34,
'purple': 35,
'cyan': 36}
def debug(msg, color=None):
if debug_colored and color:
logger.info("\033[1;%dm%s\033[0m" % (color_code[color], msg))
else:
logger.info(msg)
else:
def debug(msg, color=None):
pass
# Dico nom de fichier -> code
includes = {}
def log_traceback(fname, section, exn):
logger.error('Python %s error: %s: %s: %s' % (section, fname, str(exn.__class__).split('.', 2)[1], str(exn)))
s = StringIO()
sys.stderr = s
traceback.print_exc()
sys.stderr = sys.__stderr__
for line in s.getvalue().splitlines():
logger.error('Python %s error: -> %s' % (section, line))
def dump(env, incfile):
exec(includes[incfile], env)
def include(env, incfile):
if not incfile in env.included:
env.included.add(incfile)
exec(includes[incfile], env)
def load_file(filename, logger):
'''Charge un script et affiche un message d'erreur en cas d'exception'''
try:
return pygen.load(filename, os.path.dirname(filename) + "/." + os.path.basename(filename) + ".COMPILED", logger)
except Exception, e:
log_traceback(filename, 'compilation', e)
return None
class PythonProperties(Bcfg2.Server.Plugin.SingleXMLFileBacked):
'''Class for Python properties'''
def Index(self):
'''Build data into an elementtree object for templating usage'''
try:
self.properties = lxml.etree.XML(self.data)
del self.data
except lxml.etree.XMLSyntaxError:
logger.error("Failed to parse properties")
class FakeProperties:
'''Dummy class used when properties dont exist'''
def __init__(self):
self.properties = lxml.etree.Element("Properties")
class Python(Bcfg2.Server.Plugin.Plugin,Bcfg2.Server.Plugin.Generator):
'''The Python generator implements a templating mechanism for configuration files'''
name = 'Python'
__version__ = '1.0'
__author__ = 'dimino@crans.org'
def __init__(self, core, datastore):
Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore)
# Les entrées pour bcfg2
self.Entries['Python'] = {}
# Correspondance entrée ConfigFile -> code
self.codes = {}
# Dico ID de requête GAM -> Dossier surveillé
self.handles = {}
# Le dossier qui contient les fichiers à inclure
self.include = os.path.abspath(self.data + '/../etc/python')
self.AddDirectoryMonitor(self.data)
self.AddDirectoryMonitor(self.include)
try:
self.properties = PythonProperties('%s/../etc/properties.xml' \
% (self.data), self.core.fam)
except:
self.properties = FakeProperties()
self.logger.info("Failed to read properties file; Python properties disabled")
def BuildEntry(self, entry, metadata):
'''Construit le fichier'''
code = self.codes[entry.get('name')]
fname = entry.get('realname', entry.get('name'))
debug("building config file: %s" % fname, 'blue')
env = pygen.Environment()
env["metadata"] = metadata
env["properties"] = self.properties
env["include"] = lambda incfile: include(env, incfile)
env["dump"] = lambda incfile: dump(env, incfile)
env["info"] = { 'owner': 'root',
'group': 'root',
'perms': 0644 }
env.included = set([])
try:
include(env, "common")
text = pygen.generate(code, env, logger)
except Exception, e:
log_traceback(fname, 'exec', e)
raise Bcfg2.Server.Plugin.PluginExecutionError
info = env["info"]
if info.get('encoding', '') == 'base64':
text = binascii.b2a_base64(text)
# lxml n'accepte que de l'ascii ou de l'unicode
try:
entry.text = text.decode("UTF-8")
except:
# solution de fallback
entry.text = text.decode("ISO8859-15")
debug(entry.text)
entry.attrib['owner'] = info.get('owner', 'root')
entry.attrib['group'] = info.get('group', 'root')
entry.attrib['perms'] = oct(info.get('perms', 0644))
if 'encoding' in info:
entry.attrib['encoding'] = info['encoding']
def HandleEvent(self, event):
'''Traitement des événements de FAM'''
# On passe les fichiers ennuyeux
if event.filename[0] == '/' \
or event.filename.endswith(".COMPILED") \
or event.filename.endswith("~") \
or event.filename.endswith(".swp") \
or event.filename.startswith(".#"):
return
action = event.code2str()
debug("event received: action=%s filename=%s requestID=%s" %
(action, event.filename, event.requestID), 'purple')
path = self.handles[event.requestID] + "/" + event.filename
debug("absolute filename: %s" % path, 'yellow')
if path.startswith(self.include):
if posixpath.isfile(path):
# Les fichiers d includes...
identifier = path[len(self.include)+1:-3]
if action in ['exists', 'created', 'changed']:
debug("adding include file: %s" % identifier, 'green')
includes[identifier] = load_file(path, logger)
elif action == 'deleted':
debug("deleting include file: %s" % identifier, 'red')
del includes[identifier]
elif posixpath.isdir(path) and action in ['exists', 'created']:
self.AddDirectoryMonitor(path)
elif posixpath.isfile(path):
# Le nom du dossier est le nom du fichier du fichier de
# configuration à générer
identifier = path[len(self.data):]
if action in ['exists', 'created']:
debug("adding config file: %s" % identifier, 'green')
self.codes[identifier] = load_file(path, logger)
print "Python plugin : creating %s entry due to action %s" % (identifier, action)
self.Entries['Python'][identifier] = self.BuildEntry
elif action == 'changed':
self.codes[identifier] = load_file(path, logger)
elif action == 'deleted':
debug("deleting config file: %s" % identifier, 'red')
del self.codes[identifier]
del self.Entries['Python'][identifier]
elif posixpath.isdir(path):
if action in ['exists', 'created']:
self.AddDirectoryMonitor(path)
else:
logger.info('Ignoring file %s' % path)
def AddDirectoryMonitor(self, path):
'''Surveille un dossier avec FAM'''
if path not in self.handles.values():
if not posixpath.isdir(path):
print "Python: Failed to open directory %s" % path
return
reqid = self.core.fam.AddMonitor(path, self)
self.handles[reqid] = path

View file

@ -1,194 +0,0 @@
#!/usr/bin/env python
# -*- mode: python; coding: utf-8 -*-
#
# pygen.py
# --------
#
# Copyright (C) 2007 Jeremie Dimino <jeremie@dimino.org>
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This file is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
'''Module utilisé par le plugin Python de bcfg2 pour générer un
fichier de configuration à partir d'un script python'''
__all__ = [ "Environment", "compileSource", "generate", "load" ]
import re, marshal, os, sys
from cStringIO import StringIO
# Pour l'arrêt d'un script
class Done(Exception):
pass
def done():
raise Done()
class Environment(dict):
'''Environment dans lequel sont éxécuté les scripts'''
# Le flux de sortie
stream = None
def __init__(self):
# Création de l'environment initial
dict.__init__(self, {
# Les variables à "exporter" dans le fichier produit
"exports": set([]),
# Export d'une variable
"export": self.export,
# Dernière variable exportée à avoir été définie
"last_definition": None,
# Écrit: variable keysep tostring(value)
"defvar": self.defvar,
# La convertion en chaîne de charactère
"tostring": self.tostring,
# Définition des convertions
"conv": {bool: {True: "yes", False: "no"},
list: lambda l: ", ".join([str(x) for x in l]),
tuple: lambda l: ", ".join([str(x) for x in l])},
# Fonction de base pour imprimer quelque chose
"out": self.out,
# Arrêt du script
"done": done,
# Le séparateur pour la forme: variable keysep valeur
"keysep": "=",
# Le charactère de commentaire
"comment_start": "#"})
def __setitem__(self, variable, value):
'''Lorsqu'on définie une variable, on "l'exporte" dans
le fichier produit'''
if variable in self["exports"]:
self["defvar"](variable, value)
dict.__setitem__(self, "last_definition", variable)
dict.__setitem__(self, variable, value)
def defvar(self, variable, value):
'''Fonction par défaut pour gérée la définition des variables exportée.
écrit 'variable = tostring(value)' dans le fichier produit'''
self["out"]("%s%s%s\n" % (variable, self["keysep"], self["tostring"](value)))
def out(self, string):
'''Fonction de base pour écrire une chaine dans le fichier produit'''
self.stream.write(string)
def tostring(self, value):
'''Fonction de convertion objet python -> chaine dans un format sympa'''
convertor = self["conv"].get(type(value))
if convertor:
if type(convertor) == dict:
return convertor[value]
else:
return convertor(value)
else:
return str(value)
# Cette fonction pourrait être écrite directement dans les scripts
# mais elle est pratique donc on la met pour tout le monde
def export(self, variable):
'''Exporte une variable'''
self["exports"].add(variable)
__re_special_line = re.compile(r"^([ \t]*)(@|%)(.*)$", re.MULTILINE)
__re_affectation = re.compile(r"([a-zA-Z_][a-zA-Z_0-9]*)[ \t]*=")
__re_space_sep = re.compile(r"([^ \t]*)[ \t]+=?(.*)")
def compileSource(source, filename="", logger = None):
'''Compile un script'''
# On commence par remplacer les lignes de la forme
# @xxx par out("xxx")
newsource = StringIO()
start = 0
for m in __re_special_line.finditer(source):
newsource.write(source[start:m.start()])
start = m.end()
newsource.write(m.group(1))
linetype = m.group(2)
if linetype == "@":
line = m.group(3).replace("\\", "\\\\").replace('"', '\\"')
if line and line[0] == "#":
newsource.write('out(comment_start + "')
line = line[1:]
else:
newsource.write('out("')
newsource.write(line)
newsource.write('\\n")')
elif linetype == "%":
line = m.group(3)
m = __re_affectation.match(line)
if m:
varname = m.group(1)
newsource.write(line)
newsource.write("; defvar('")
newsource.write(varname)
newsource.write("', tostring(")
newsource.write(varname)
newsource.write("))\n")
else:
m = __re_space_sep.match(line)
newsource.write("defvar('")
newsource.write(m.group(1))
newsource.write("', tostring(")
newsource.write(m.group(2))
newsource.write("))\n")
newsource.write(source[start:])
if logger:
try:
logger.info(newsource.getvalue())
except:
print "Le logger de BCFG2 c'est de la merde, il refuse le non ascii."
print "Voici ce que j'ai essayé de logguer."
print newsource.getvalue()
return compile(newsource.getvalue(), filename, "exec")
def generate(code, environment=None, logger = None):
'''Évalue un script'''
if type(code) == str:
code = compileSource(code, logger = logger)
if not environment:
environment = Environment()
environment.stream = StringIO()
save_stdout = sys.stdout
sys.stdout = environment.stream
try:
exec(code, environment)
except Done, _:
pass
except Exception, exn:
sys.stderr.write('code: %r\n' % code)
sys.stdout = save_stdout
raise
sys.stdout = save_stdout
return environment.stream.getvalue()
def load(fname, cfname=None, logger=None):
'''Charge un script et le compile, en créant/utilisant un fichier de
cache'''
if not cfname:
cfname = fname + 'c'
if os.path.exists(cfname) and os.stat(fname).st_mtime <= os.stat(cfname).st_mtime:
code = marshal.load(file(cfname))
else:
code = compileSource(file(fname).read(), fname, logger)
cfile = open(cfname, "w")
marshal.dump(code, cfile)
cfile.close()
return code
if __name__ == "__main__":
result = generate(load(sys.argv[1]))
print "resultat:"
sys.stdout.write(result)