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 python libs
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
# Make sure augeas python interface is installed
|
# Make sure augeas python interface is installed
|
||||||
HAS_AUGEAS = False
|
HAS_AUGEAS = False
|
||||||
@ -38,6 +40,8 @@ except ImportError:
|
|||||||
# Import salt libs
|
# Import salt libs
|
||||||
from salt.exceptions import SaltInvocationError
|
from salt.exceptions import SaltInvocationError
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Define the module's virtual name
|
# Define the module's virtual name
|
||||||
__virtualname__ = 'augeas'
|
__virtualname__ = 'augeas'
|
||||||
|
|
||||||
@ -81,6 +85,85 @@ def _lstrip_word(word, prefix):
|
|||||||
return word
|
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=''):
|
def get(path, value=''):
|
||||||
'''
|
'''
|
||||||
Get a value for a specific augeas path
|
Get a value for a specific augeas path
|
||||||
|
@ -9,9 +9,7 @@ This state requires the ``augeas`` Python module.
|
|||||||
|
|
||||||
.. _Augeas: http://augeas.net/
|
.. _Augeas: http://augeas.net/
|
||||||
|
|
||||||
Augeas_ can be used to manage configuration files. Currently only the ``set``
|
Augeas_ can be used to manage configuration files.
|
||||||
command is supported via this state. The :mod:`augeas
|
|
||||||
<salt.modules.augeas_cfg>` module also has support for get, match, remove, etc.
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
@ -29,54 +27,185 @@ command is supported via this state. The :mod:`augeas
|
|||||||
For affected Debian/Ubuntu hosts, installing ``libpython2.7`` has been
|
For affected Debian/Ubuntu hosts, installing ``libpython2.7`` has been
|
||||||
known to resolve the issue.
|
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__():
|
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):
|
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
|
Set a value for a specific augeas path
|
||||||
'''
|
'''
|
||||||
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
|
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
|
||||||
|
Loading…
Reference in New Issue
Block a user