> I'm thinking it's about time for another release. > (...) > So if anyone has anything outstanding (patches, modules, > must have features, must fix bugs, docs, etc) it would be > great if you could get it ready soon ;-) >(...) > Adrian
Hello, so here is a new module proposal. This is a module to handle (get, set, list...) parameters in configuration files. It relies on augeas (http://augeas.net/). I wanted to post later, because in comparison with augeas.py I didn't make the move and insert methods, because I have improvements in mind, and because I want to add more behaviour consistency with augtool of Augeas. But I will not have time for this before Christmas, and the module is working fine as-is. A quick example: #!/usr/bin/env python import sys import func.overlord.client as fc c = fc.Client("*") print 'Set PermitRootLogin to no in sshd_config' print c.confmgt_augeas.set('/etc/ssh/sshd_config','PermitRootLogin','no') (which does what is expected, of course) In attachment you'll find: - the module - a test script I use to check the behaviour of the module - the result of this test script on my test platform (so you can see what the module does) - a minimal doc. inspired from the quick tour on the augeas web site Let me know if you think it is useful. Louis Coilliot
#!/usr/bin/env python
#
# Copyright 2008
# Louis Coilliot <[EMAIL PROTECTED]>
#
# This software may be freely redistributed under the terms of the GNU
# general public license.
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
import func_module
from os import path as ospath
def lstripstr(the_string,the_prefix):
"""Return a copy of the string with leading prefix removed."""
if the_string.startswith(the_prefix):
return the_string[len(the_prefix):]
return the_string
def recurmatch(aug, path):
"""Generate all tree children of a start path."""
#Function adapted from test_augeas.py in python-augeas-0.3.0
#Original Author: Harald Hoyer <[EMAIL PROTECTED]>"""
if path:
if path != "/":
val = aug.get(path)
if val:
yield (path, val)
# here is the modification to (almost) match augtool print behavior:
else:
yield (path, '(none)')
# end of modification
m = []
if path != "/":
aug.match(path)
for i in m:
for x in recurmatch(aug, i):
yield x
else:
for i in aug.match(path + "/*"):
for x in recurmatch(aug, i):
yield x
class confMgtAug(func_module.FuncModule):
version = "0.0.1"
api_version = "0.0.1"
description = "Manage parameters in configuration files, with the help of Augeas."
def get(self,entryPath,param='',hierarchy='/files'):
"""Get a value for a config. parameter in a config. file,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
path=hierarchy+entryPath+'/'
try:
matchtest=aug.match(path+param)
except Exception, e: return str(e)
if matchtest:
try:
pvalue=aug.get(path+param)
#aug.close()
except Exception, e: return str(e)
else:
# The node doesn't exist
pvalue='(o)'
if not pvalue:
# The node doesn't have a value
pvalue='(none)'
return { 'path': entryPath, 'parameter': param, 'value': pvalue }
def set(self,entryPath,param='',pvalue='',hierarchy='/files'):
"""Set/change a value for a config. parameter in a config. file,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
path=hierarchy+entryPath
try:
aug.set(path+"/"+param,pvalue)
except Exception, e: return str(e)
# Here is a little workaround for a bug in save for augeas 0.3.2.
# In the future this won't be necessary anymore.
try:
aug.save()
except:
pass
# End of workaround
try:
aug.save()
except Exception, e: return str(e)
try:
pvalue=aug.get(path+"/"+param)
#aug.close()
except Exception, e: return str(e)
return { 'path': entryPath, 'parameter': param, 'value': pvalue }
def match(self,entryPath,param='',pvalue='',hierarchy='/files'):
"""Match a value for a config. parameter in a config. file,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
path=hierarchy+entryPath
childpath=path+'/*/'
if pvalue:
try:
matchlist = [ ospath.dirname(lstripstr(item,'/files')) for item in aug.match(path+'/'+param) + aug.match(childpath+'/'+param) if ( aug.get(item) == pvalue ) ]
#aug.close()
except Exception, e: return str(e)
else:
try:
matchlist = [ ospath.dirname(lstripstr(item,'/files')) for item in aug.match(path+'/'+param) + aug.match(childpath+'/'+param) ]
#aug.close()
except Exception, e: return str(e)
return matchlist
def ls(self,entryPath,hierarchy='/files'):
"""List the direct children of an entry in a config. file,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
path=hierarchy+entryPath
# We can't use a dict here because the same key can appear many times.
nodes=[]
try:
for match in aug.match(path+'/*'):
pvalue=aug.get(match)
if not pvalue:
pvalue='(none)'
nodes.append([ospath.basename(match),pvalue])
except Exception, e: return str(e)
#try:
# aug.close()
#except Exception, e: return str(e)
return { 'path': entryPath, 'nodes': nodes }
# print is a reserved word so we use printconf instead
def printconf(self,entryPath,hierarchy='/files'):
"""Print all tree children nodes from the path provided,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
path=hierarchy+entryPath
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
matches = recurmatch(aug, path)
# Here we loose the benefit of the generator function:
return { 'path': entryPath, 'nodes':[ [lstripstr(p,'/files'),attr] for (p,attr) in matches ] }
def rm(self,entryPath,param='',hierarchy='/files'):
"""Delete a parameter (and all its children) in a config. file,
with the help of Augeas, a configuration API (cf http://augeas.net)"""
try:
from augeas import Augeas
aug=Augeas()
except Exception, e: return str(e)
path=hierarchy+entryPath
try:
result=aug.remove(path+"/"+param)
#aug.close()
except Exception, e: return str(e)
# Here is a little workaround for a bug in save for augeas 0.3.2.
# In the future this won't be necessary anymore.
try:
aug.save()
except:
pass
# End of workaround
try:
aug.save()
except Exception, e: return str(e)
if result == -1:
msg = 'Invalid node'
else:
msg = repr(result)+' node(s) removed.'
return msg
def register_method_args(self):
"""
Implementing the method arg getter
"""
return {
'get':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'param':{
'type':'string',
'optional':True,
'default':'',
'description':'The target parameter in the config. file'
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"Get a value for a config. parameter in a config. file."
},
'set':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'param':{
'type':'string',
'optional':True,
'default':'',
'description':'The target parameter in the config. file'
},
'pvalue':{
'type':'string',
'optional':True,
'default':'',
'description':'The value to set for the parameter in the config. file'
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"Set/change a value for a config. parameter in a config. file."
},
'match':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'param':{
'type':'string',
'optional':True,
'default':'',
'description':'The target parameter in the config. file'
},
'pvalue':{
'type':'string',
'optional':True,
'default':'',
'description':'The value to set for the parameter in the config. file'
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"Match a value for a config. parameter in a config. file."
},
'ls':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"List the direct children of an entry in a config. file."
},
'printconf':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"Print all tree children nodes from the path provided."
},
'rm':{
'args':{
'entryPath':{
'type':'string',
'optional':False,
'description':'The path to the config. file (fs or Augeas path)',
},
'param':{
'type':'string',
'optional':True,
'default':'',
'description':'The target parameter in the config. file'
},
'hierarchy':{
'type':'string',
'optional':True,
'default':'/files',
'description':'The augeas base path hierarchy'
}
},
'description':"Delete a parameter (and all its children) in a config. file."
}
}
Here is a brief overview of using the module confmgt_augeas for func. This module relies on Augeas (htt://augeas.net). Augeas is a configuration API, for handling (set, get, list...) the parameters of configuration files on the func minions. It is inspired from the 'Quick tour' of Augeas (http://augeas.net/tour.html) Below, the guinea pig minion is named 'kermit'. - Set a parameter in a configuration file: i.e. PermitRootLogin yes in sshd_config func 'kermit' call confmgt_augeas set '/etc/ssh/sshd_config' 'PermitRootLogin' 'yes' {'kermit': {'parameter': 'PermitRootLogin', 'path': '/etc/ssh/sshd_config', 'value': 'yes'} } The arguments are config. file, parameter and value. - Get a parameter in a configuration file: i.e. port in sshd_config func 'kermit' call confmgt_augeas get '/etc/ssh/sshd_config' 'Port' The arguments are config. file and parameter. {'kermit': {'parameter': 'Port', 'path': '/etc/ssh/sshd_config', 'value': '22'} } Most actions involve a file, a parameter, and a value. But Augeas purists would perhaps prefer using the very graceful path-like syntax of Augeas. They can do so: func 'kermit' call confmgt_augeas get '/etc/ssh/sshd_config/Port' {'kermit': {'parameter': 'Port', 'path': '/etc/ssh/sshd_config', 'value': '22'} } - Make sshd accept an additional environment variable The example is in python this time. In sshd_config some settings can be repeated in the file, and values are accumulated. These values are best viewed as arrays. To illustrate this, we will add a new environment variable FOO to the AcceptEnv setting in /etc/ssh/sshd_config. These values are mapped into a tree (see http://augeas.net for more details on augeas schemas, tree an d path expressions). import func.overlord.client as fc c = fc.Client("kermit") print c.confmgt_augeas.printconf('/etc/ssh/sshd_config/AcceptEnv') If sshd_config on minion 'kermit' contains: AcceptEnv LANG LC_CTYPE AcceptEnv LC_IDENTIFICATION LC_ALL FOO You'll get: {'kermit': {'path': '/etc/ssh/sshd_config/AcceptEnv', 'nodes': [ ['/etc/ssh/sshd_config/AcceptEnv', '(none)'], ['/etc/ssh/sshd_config/AcceptEnv[1]/1', 'LANG'], ['/etc/ssh/sshd_config/AcceptEnv[1]/2', 'LC_CTYPE'], ['/etc/ssh/sshd_config/AcceptEnv[2]/3', 'LC_IDENTIFICATION'], ['/etc/ssh/sshd_config/AcceptEnv[2]/4', 'LC_ALL'], ] } } To add a new variable FOO at the end of the last AcceptEnv line, we perform print c.confmgt_augeas.set('/etc/ssh/sshd_config/AcceptEnv[last()]','10000','FOO') Which gives: {'kermit': {'path': '/etc/ssh/sshd_config/AcceptEnv[last()]', 'parameter': '10000', 'value': 'FOO'}} After the action (on the target minion), sshd_config contains: AcceptEnv LANG LC_CTYPE AcceptEnv LC_IDENTIFICATION LC_ALL FOO The addition of [last()] to AcceptEnv in the path tells Augeas that we are talking about the last node named AcceptEnv. Augeas requires that for a set. The path expression corresponds either to an existing node, or to no node at all (in which case a new node is created). '10000' is 'very big' to be sure we add the value in last position.
#!/usr/bin/env python
import sys
import func.overlord.client as fc
c = fc.Client("*")
print 'Delete the Parameter PermitRootLogin in sshd_config'
print c.confmgt_augeas.rm('/etc/ssh/sshd_config','PermitRootLogin')
print
print 'Delete the Parameter Port in sshd_config with an Augeas-style path'
print c.confmgt_augeas.rm('/etc/ssh/sshd_config/Port')
print
print 'Get sshd_config Port value.'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','Port')
print
print 'Set Port to 22 in sshd_config'
print c.confmgt_augeas.set('/etc/ssh/sshd_config','Port','22')
print
print 'Get sshd_config Port value.'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','Port')
print
print 'Try to delete a non existant parameter in sshd_config'
print c.confmgt_augeas.rm('/etc/ssh/sshd_config','Nawak')
print
print 'Try to delete a parameter in a non existant file.'
print c.confmgt_augeas.rm('/etc/ssh/nimp','Nawak')
print
print 'Get sshd_config PermitRootLogin value.'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','PermitRootLogin')
print
print 'Set PermitRootLogin to yes in sshd_config'
print c.confmgt_augeas.set('/etc/ssh/sshd_config','PermitRootLogin','yes')
print
print 'Set PermitRootLogin to no in sshd_config with an Augeas-style path.'
print c.confmgt_augeas.set('/etc/ssh/sshd_config/PermitRootLogin','','no')
print
print 'Set PermitRootLogin to yes in sshd_config with an Augeas-style path.'
print c.confmgt_augeas.set('/etc/ssh/sshd_config/PermitRootLogin','','yes')
print
print 'Get sshd_config PermitRootLogin value.'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','PermitRootLogin')
print
print 'Get sshd_config PermitRootLogin value with an Augeas-style path.'
print c.confmgt_augeas.get('/etc/ssh/sshd_config/PermitRootLogin')
print
print 'Attempt to get a value for a non existant param in sshd_config'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','Nawak')
print
print 'Attempt to get a value for an empty param in sshd_config'
print c.confmgt_augeas.get('/etc/ssh/sshd_config','Subsystem')
print
print 'Search for conf. entry in hosts file with canonical hostname = pim'
print c.confmgt_augeas.match('/etc/hosts','canonical','pim')
print
#print 'List all direct children of hosts (not very useful)'
#print c.confmgt_augeas.ls('/etc/hosts/*')
#print
print 'List all direct children parameters of 1st hosts entry.'
for host,paramlist in c.confmgt_augeas.ls('/etc/hosts/1').iteritems():
print "Host: "+host
for node in paramlist['nodes']:
print node[0]+" = "+node[1]
print
print 'List all children nodes of 1st hosts entry.'
for host,paramlist in c.confmgt_augeas.printconf('/etc/hosts/1').iteritems():
print "Host: "+host
for node in paramlist['nodes']:
print node[0]+" = "+node[1]
print
print 'Get values of 1st host entry.'
print c.confmgt_augeas.get('/etc/hosts/','1')
print
print 'List all values for parameter of 1st fstab entry.'
minionDict=c.confmgt_augeas.ls('/etc/fstab/1')
for host,entry in minionDict.iteritems():
print "Host: "+host
print "Entry path: "+entry['path']
for node in entry['nodes']:
print node[0]+" = "+node[1]
print
print 'Get ipaddr of /etc/hosts 1st entry.'
print c.confmgt_augeas.get('/etc/hosts/1','ipaddr')
print
#
#print 'List all direct children parameters of sshd_config'
#for host,paramlist in c.confmgt_augeas.ls('/etc/ssh/sshd_config').iteritems():
# print "Host: "+host
# for node in paramlist['nodes']:
# print node[0]+" = "+node[1]
#print
#
#print 'List all children nodes of sshd_config'
#for host,paramlist in c.confmgt_augeas.printconf('/etc/ssh/sshd_config').iteritems():
# print "Host: "+host
# for node in paramlist['nodes']:
# print node[0]+" = "+node[1]
#print
#
print 'List all direct children of AcceptEnv entries in sshd_config'
for host,paramlist in c.confmgt_augeas.ls('/etc/ssh/sshd_config/AcceptEnv').iteritems():
print "Host: "+host
for node in paramlist['nodes']:
print node[0]+" = "+node[1]
print
print 'See all AcceptEnv entries in sshd_config'
for host,paramlist in c.confmgt_augeas.printconf('/etc/ssh/sshd_config/AcceptEnv').iteritems():
print "Host: "+host
for node in paramlist['nodes']:
print node[0]+" = "+node[1]
print
print 'Try to match PermitRootLogin yes in sshd_config'
print c.confmgt_augeas.match('/etc/ssh/sshd_config','PermitRootLogin','yes')
print
print 'Try to match PermitRootLogin yes in sshd_config with an Augeas-style path'
print c.confmgt_augeas.match('/etc/ssh/sshd_config/PermitRootLogin','','yes')
print
print 'Try to match PermitRootLogin yes in some config. files.'
print c.confmgt_augeas.match('/etc/*/*','PermitRootLogin','yes')
print
print 'Try to match AcceptEnv in sshd_config'
print c.confmgt_augeas.match('/etc/ssh/sshd_config','AcceptEnv')
print
print 'Try to match PermitRootLogin in sshd_config'
print c.confmgt_augeas.match('/etc/ssh/sshd_config','PermitRootLogin')
print
print 'Try to match PermitRootLogin in sshd_config with an Augeas-style path.'
print c.confmgt_augeas.match('/etc/ssh/sshd_config/PermitRootLogin')
print
print 'Try to match canonical entries in hosts file.'
print c.confmgt_augeas.match('/etc/hosts','canonical')
print
print 'Try to match canonical entries in hosts file with an Augeas-style path.'
print c.confmgt_augeas.match('/etc/hosts/*/canonical')
print
print 'Augeas metainformation.'
print c.confmgt_augeas.ls('/','augeas')
print c.confmgt_augeas.get('/','save','augeas')
print c.confmgt_augeas.set('/','save','backup','augeas')
print c.confmgt_augeas.get('/','save','augeas')
print c.confmgt_augeas.get('/files/etc/hosts/lens/','info','augeas')
print
print 'Change the (canonical) hostname associated to a specific IP in hosts file.'
hostfile='/etc/hosts'
ip='192.168.0.253'
newCanonical='fozzie'
#newCanonical='piggy'
# We search which entry in /etc/hosts refers to the IP
ipmatch = c.confmgt_augeas.match(hostfile,'ipaddr',ip)
# for each minion concerned
for host,entry in ipmatch.iteritems():
# The first and unique entry in the list, entry[0], is what we searched for
# We check that the target canonical hostname is not already set
oldCanonical=c.confmgt_augeas.get(entry[0],'canonical')[host]['value']
if oldCanonical != newCanonical:
print c.confmgt_augeas.set(entry[0],'canonical',newCanonical)
else:
print 'Nothing to do'
print
print 'Add a new variable FOO at the end of the last AcceptEnv line of sshd_config'
print "And we don't want to do this twice."
foomatch=c.confmgt_augeas.match('/etc/ssh/sshd_config','AcceptEnv/*','FOO')
for host,matchlist in foomatch.iteritems():
if not matchlist:
client = fc.Client(host)
print client.confmgt_augeas.set('/etc/ssh/sshd_config/AcceptEnv[last()]','10000','FOO')
print
test-confmgt_augeas.out
Description: Binary data
_______________________________________________ Func-list mailing list [email protected] https://www.redhat.com/mailman/listinfo/func-list
