mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge pull request #13773 from pkruithof/issue-7610-augeas-lenses
Added improvements to Augeas module/state
This commit is contained in:
commit
0ab251c214
@ -26,6 +26,8 @@ This module requires the ``augeas`` Python module.
|
||||
|
||||
# Import python libs
|
||||
import os
|
||||
import re
|
||||
import logging
|
||||
|
||||
# Make sure augeas python interface is installed
|
||||
HAS_AUGEAS = False
|
||||
@ -38,6 +40,8 @@ except ImportError:
|
||||
# Import salt libs
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'augeas'
|
||||
|
||||
@ -81,6 +85,85 @@ def _lstrip_word(word, prefix):
|
||||
return word
|
||||
|
||||
|
||||
def execute(context=None, lens=None, commands=()):
|
||||
'''
|
||||
Execute Augeas commands
|
||||
'''
|
||||
ret = {'retval': False}
|
||||
|
||||
method_map = {
|
||||
'set': 'set',
|
||||
'mv': 'move',
|
||||
'move': 'move',
|
||||
'ins': 'insert',
|
||||
'insert': 'insert',
|
||||
'rm': 'remove',
|
||||
'remove': 'remove',
|
||||
}
|
||||
|
||||
flags = Augeas.NO_MODL_AUTOLOAD if lens else Augeas.NONE
|
||||
aug = Augeas(flags=flags)
|
||||
|
||||
if lens:
|
||||
aug.add_transform(lens, re.sub('^/files', '', context))
|
||||
aug.load()
|
||||
|
||||
for command in commands:
|
||||
# first part up to space is always the command name (ie: set, move)
|
||||
cmd, arg = command.split(' ', 1)
|
||||
if not cmd in method_map:
|
||||
ret['error'] = 'Command {0} is not supported (yet)'.format(cmd)
|
||||
return ret
|
||||
|
||||
method = method_map[cmd]
|
||||
|
||||
try:
|
||||
if method == 'set':
|
||||
path, value, remainder = re.split('([^\'" ]+|"[^"]+"|\'[^\']+\')$', arg, 1)
|
||||
if context:
|
||||
path = os.path.join(context.rstrip('/'), path.lstrip('/'))
|
||||
value = value.strip('"').strip("'")
|
||||
args = {'path': path, 'value': value}
|
||||
elif method == 'move':
|
||||
path, dst = arg.split(' ', 1)
|
||||
if context:
|
||||
path = os.path.join(context.rstrip('/'), path.lstrip('/'))
|
||||
args = {'src': path, 'dst': dst}
|
||||
elif method == 'insert':
|
||||
path, where, label = re.split(' (before|after) ', arg)
|
||||
if context:
|
||||
path = os.path.join(context.rstrip('/'), path.lstrip('/'))
|
||||
args = {'path': path, 'label': label, 'before': where == 'before'}
|
||||
elif method == 'remove':
|
||||
path = arg
|
||||
if context:
|
||||
path = os.path.join(context.rstrip('/'), path.lstrip('/'))
|
||||
args = {'path': path}
|
||||
except ValueError as err:
|
||||
log.error(str(err))
|
||||
ret['error'] = 'Invalid formatted command, ' \
|
||||
'see debug log for details: {0}'.format(arg)
|
||||
return ret
|
||||
|
||||
log.debug('{0}: {1}'.format(method, args))
|
||||
|
||||
func = getattr(aug, method)
|
||||
func(**args)
|
||||
|
||||
try:
|
||||
aug.save()
|
||||
ret['retval'] = True
|
||||
except IOError as err:
|
||||
ret['error'] = str(err)
|
||||
|
||||
if lens and not lens.endswith('.lns'):
|
||||
ret['error'] += '\nLenses are normally configured as "name.lns". ' \
|
||||
'Did you mean "{0}.lns"?'.format(lens)
|
||||
|
||||
aug.close()
|
||||
return ret
|
||||
|
||||
|
||||
def get(path, value=''):
|
||||
'''
|
||||
Get a value for a specific augeas path
|
||||
|
@ -9,9 +9,7 @@ This state requires the ``augeas`` Python module.
|
||||
|
||||
.. _Augeas: http://augeas.net/
|
||||
|
||||
Augeas_ can be used to manage configuration files. Currently only the ``set``
|
||||
command is supported via this state. The :mod:`augeas
|
||||
<salt.modules.augeas_cfg>` module also has support for get, match, remove, etc.
|
||||
Augeas_ can be used to manage configuration files.
|
||||
|
||||
.. warning::
|
||||
|
||||
@ -29,54 +27,185 @@ command is supported via this state. The :mod:`augeas
|
||||
For affected Debian/Ubuntu hosts, installing ``libpython2.7`` has been
|
||||
known to resolve the issue.
|
||||
|
||||
|
||||
Usage examples:
|
||||
|
||||
1. Set the first entry in ``/etc/hosts`` to ``localhost``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hosts:
|
||||
augeas.setvalue:
|
||||
- changes:
|
||||
- /files/etc/hosts/1/canonical: localhost
|
||||
|
||||
2. Add a new host to ``/etc/hosts`` with the IP address ``192.168.1.1`` and
|
||||
hostname ``test``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
hosts:
|
||||
augeas.setvalue:
|
||||
- changes:
|
||||
- /files/etc/hosts/2/ipaddr: 192.168.1.1
|
||||
- /files/etc/hosts/2/canonical: foo.bar.com
|
||||
- /files/etc/hosts/2/alias[1]: foosite
|
||||
- /files/etc/hosts/2/alias[2]: foo
|
||||
|
||||
|
||||
A prefix can also be set, to avoid redundancy:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
nginx-conf:
|
||||
augeas.setvalue:
|
||||
- prefix: /files/etc/nginx/nginx.conf
|
||||
- changes:
|
||||
- user: www-data
|
||||
- worker_processes: 2
|
||||
- http/server_tokens: off
|
||||
- http/keepalive_timeout: 65
|
||||
|
||||
'''
|
||||
|
||||
import re
|
||||
import os.path
|
||||
import difflib
|
||||
|
||||
def __virtual__():
|
||||
return 'augeas' if 'augeas.setvalue' in __salt__ else False
|
||||
return 'augeas' if 'augeas.execute' in __salt__ else False
|
||||
|
||||
|
||||
def change(name, context=None, changes=None, lens=None, **kwargs):
|
||||
'''
|
||||
.. versionadded:: 2014.1.6
|
||||
This state replaces :py:func:`~salt.states.augeas.setvalue`.
|
||||
|
||||
Issue changes to Augeas, optionally for a specific context, with a
|
||||
specific lens.
|
||||
|
||||
name
|
||||
State name
|
||||
|
||||
context
|
||||
The context to use. Set this to a file path, prefixed by ``/files``, to
|
||||
avoid redundancy, eg:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
redis-conf:
|
||||
augeas.change:
|
||||
- context: /files/etc/redis/redis.conf
|
||||
- changes:
|
||||
- set bind 0.0.0.0
|
||||
- set maxmemory 1G
|
||||
|
||||
changes
|
||||
List of changes that are issued to Augeas. Available commands are
|
||||
``set``, ``mv``/``move``, ``ins``/``insert``, and ``rm``/``remove``.
|
||||
|
||||
lens
|
||||
The lens to use, needs to be suffixed with `.lns`, eg: `Nginx.lns`. See
|
||||
the `list of stock lenses <http://augeas.net/stock_lenses.html>`_
|
||||
shipped with Augeas.
|
||||
|
||||
|
||||
Usage examples:
|
||||
|
||||
Set the ``bind`` parameter in ``/etc/redis/redis.conf``:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
redis-conf:
|
||||
augeas.change:
|
||||
- changes:
|
||||
- set /files/etc/redis/redis.conf/bind 0.0.0.0
|
||||
|
||||
.. note::
|
||||
|
||||
Use the ``context`` parameter to specify the file you want to
|
||||
manipulate. This way you don't have to include this in the changes
|
||||
every time:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
redis-conf:
|
||||
augeas.change:
|
||||
- context: /files/etc/redis/redis.conf
|
||||
- changes:
|
||||
- set bind 0.0.0.0
|
||||
- set databases 4
|
||||
- set maxmemory 1G
|
||||
|
||||
Augeas is aware of a lot of common configuration files and their syntax.
|
||||
It knows the difference between for example ini and yaml files, but also
|
||||
files with very specific syntax, like the hosts file. This is done with
|
||||
*lenses*, which provide mappings between the Augeas tree and the file.
|
||||
|
||||
There are many `preconfigured lenses`_ that come with Augeas by default,
|
||||
and they specify the common locations for configuration files. So most
|
||||
of the time Augeas will know how to manipulate a file. In the event that
|
||||
you need to manipulate a file that Augeas doesn't know about, you can
|
||||
specify the lens to use like this:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
redis-conf:
|
||||
augeas.change:
|
||||
- lens: redis
|
||||
- context: /files/etc/redis/redis.conf
|
||||
- changes:
|
||||
- set bind 0.0.0.0
|
||||
|
||||
.. note::
|
||||
|
||||
Even though Augeas knows that ``/etc/redis/redis.conf`` is a Redis
|
||||
configuration file and knows how to parse it, it is recommended to
|
||||
specify the lens anyway. This is because by default, Augeas loads all
|
||||
known lenses and their associated file paths. All these files are
|
||||
parsed when Augeas is loaded, which can take some time. When specifying
|
||||
a lens, Augeas is loaded with only that lens, which speeds things up
|
||||
quite a bit.
|
||||
|
||||
.. _preconfigured lenses: http://augeas.net/stock_lenses.html
|
||||
|
||||
A more complex example, this adds an entry to the services file for Zabbix,
|
||||
and removes an obsolete service:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
zabbix-service:
|
||||
augeas.change:
|
||||
- lens: services
|
||||
- context: /files/etc/services
|
||||
- changes:
|
||||
- ins service-name after service-name[last()]
|
||||
- set service-name[last()] zabbix-agent
|
||||
- set service-name[. = 'zabbix-agent']/#comment "Zabbix Agent service"
|
||||
- set service-name[. = 'zabbix-agent']/port 10050
|
||||
- set service-name[. = 'zabbix-agent']/protocol tcp
|
||||
- rm service-name[. = 'im-obsolete']
|
||||
- unless: grep "zabbix-agent" /etc/services
|
||||
|
||||
.. warning::
|
||||
|
||||
Don't forget the ``unless`` here, otherwise a new entry will be added
|
||||
everytime this state is run.
|
||||
|
||||
'''
|
||||
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
|
||||
|
||||
if not changes or not isinstance(changes, list):
|
||||
ret['comment'] = '\'changes\' must be specified as a list'
|
||||
return ret
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Executing commands'
|
||||
if context:
|
||||
ret['comment'] += ' in file "{1}"'.format(context)
|
||||
ret['comment'] += "\n".join(changes)
|
||||
return ret
|
||||
|
||||
old_file = []
|
||||
if context:
|
||||
filename = re.sub('^/files|/$', '', context)
|
||||
if os.path.isfile(filename):
|
||||
file = open(filename, 'r')
|
||||
old_file = file.readlines()
|
||||
file.close()
|
||||
|
||||
result = __salt__['augeas.execute'](context=context, lens=lens, commands=changes)
|
||||
ret['result'] = result['retval']
|
||||
|
||||
if ret['result'] is False:
|
||||
ret['comment'] = 'Error: {0}'.format(result['error'])
|
||||
return ret
|
||||
|
||||
if old_file:
|
||||
file = open(filename, 'r')
|
||||
diff = ''.join(difflib.unified_diff(old_file, file.readlines(), n=0))
|
||||
file.close()
|
||||
|
||||
if diff:
|
||||
ret['comment'] = 'Changes have been saved'
|
||||
ret['changes'] = diff
|
||||
else:
|
||||
ret['comment'] = 'No changes made'
|
||||
|
||||
else:
|
||||
ret['comment'] = 'Changes have been saved'
|
||||
ret['changes'] = changes
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def setvalue(name, prefix=None, changes=None, **kwargs):
|
||||
'''
|
||||
.. deprecated:: 2014.1.6
|
||||
Use :py:func:`~salt.states.augeas.change` instead.
|
||||
|
||||
Set a value for a specific augeas path
|
||||
'''
|
||||
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
|
||||
|
Loading…
Reference in New Issue
Block a user