Module for func using augeas

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



> 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 <louis.coilliot@xxxxxxxxxxxx>
#
# 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 <harald@xxxxxxxxxx>"""
    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

Attachment: test-confmgt_augeas.out
Description: Binary data

_______________________________________________
Func-list mailing list
Func-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/func-list

[Index of Archives]     [Fedora Users]     [Linux Networking]     [Fedora Legacy List]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]

  Powered by Linux