Vieux plugin Python aux archives
This commit is contained in:
parent
3fb338e937
commit
1df9d480af
8 changed files with 0 additions and 0 deletions
683
archive/bcfg2/Tools/Python.py
Normal file
683
archive/bcfg2/Tools/Python.py
Normal file
|
@ -0,0 +1,683 @@
|
|||
"""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, _)
|
57
archive/bcfg2/backup.py
Executable file
57
archive/bcfg2/backup.py
Executable file
|
@ -0,0 +1,57 @@
|
|||
#!/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
|
||||
|
66
archive/bcfg2/bcfg2-graph.py
Executable file
66
archive/bcfg2/bcfg2-graph.py
Executable file
|
@ -0,0 +1,66 @@
|
|||
#!/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()
|
52
archive/bcfg2/bcfg2_report.py
Executable file
52
archive/bcfg2/bcfg2_report.py
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/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="")
|
3
archive/bcfg2/clean_dotcompiled
Executable file
3
archive/bcfg2/clean_dotcompiled
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
|
||||
find /var/lib/bcfg2 -name "*.COMPILED" -exec rm -f '{}' \;
|
26
archive/bcfg2/plugins/DebAutoPkg.py
Normal file
26
archive/bcfg2/plugins/DebAutoPkg.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# -*- 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'
|
222
archive/bcfg2/plugins/Python.py
Normal file
222
archive/bcfg2/plugins/Python.py
Normal file
|
@ -0,0 +1,222 @@
|
|||
# -*- 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
|
194
archive/bcfg2/pygen.py
Executable file
194
archive/bcfg2/pygen.py
Executable file
|
@ -0,0 +1,194 @@
|
|||
#!/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)
|
Loading…
Add table
Add a link
Reference in a new issue