mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
parent
454efa1688
commit
98593a94d5
@ -49,6 +49,17 @@ log = logging.getLogger(__name__)
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'augeas'
|
||||
|
||||
METHOD_MAP = {
|
||||
'set': 'set',
|
||||
'setm': 'setm',
|
||||
'mv': 'move',
|
||||
'move': 'move',
|
||||
'ins': 'insert',
|
||||
'insert': 'insert',
|
||||
'rm': 'remove',
|
||||
'remove': 'remove',
|
||||
}
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
@ -89,6 +100,13 @@ def _lstrip_word(word, prefix):
|
||||
return word
|
||||
|
||||
|
||||
def method_map():
|
||||
'''
|
||||
Make METHOD_MAP accessible via __salt__['augeas.method_map']()
|
||||
'''
|
||||
return METHOD_MAP
|
||||
|
||||
|
||||
def execute(context=None, lens=None, commands=()):
|
||||
'''
|
||||
Execute Augeas commands
|
||||
@ -103,17 +121,6 @@ def execute(context=None, lens=None, commands=()):
|
||||
'''
|
||||
ret = {'retval': False}
|
||||
|
||||
method_map = {
|
||||
'set': 'set',
|
||||
'setm': 'setm',
|
||||
'mv': 'move',
|
||||
'move': 'move',
|
||||
'ins': 'insert',
|
||||
'insert': 'insert',
|
||||
'rm': 'remove',
|
||||
'remove': 'remove',
|
||||
}
|
||||
|
||||
arg_map = {
|
||||
'set': (1, 2),
|
||||
'setm': (2, 3),
|
||||
@ -123,33 +130,42 @@ def execute(context=None, lens=None, commands=()):
|
||||
}
|
||||
|
||||
def make_path(path):
|
||||
'''
|
||||
Return correct path
|
||||
'''
|
||||
if not context:
|
||||
return path
|
||||
path = path.lstrip('/')
|
||||
if path:
|
||||
|
||||
if path.lstrip('/'):
|
||||
if path.startswith(context):
|
||||
return path
|
||||
|
||||
path = path.lstrip('/')
|
||||
return os.path.join(context, path)
|
||||
else:
|
||||
return context
|
||||
|
||||
flags = _Augeas.NO_MODL_AUTOLOAD if lens else _Augeas.NONE
|
||||
flags = _Augeas.NO_MODL_AUTOLOAD if lens and context else _Augeas.NONE
|
||||
aug = _Augeas(flags=flags)
|
||||
|
||||
if lens:
|
||||
if lens and context:
|
||||
aug.add_transform(lens, re.sub('^/files', '', context))
|
||||
aug.load()
|
||||
|
||||
for command in commands:
|
||||
# first part up to space is always the command name (i.e.: set, move)
|
||||
cmd, arg = command.split(' ', 1)
|
||||
if cmd not in method_map:
|
||||
ret['error'] = 'Command {0} is not supported (yet)'.format(cmd)
|
||||
return ret
|
||||
|
||||
method = method_map[cmd]
|
||||
nargs = arg_map[method]
|
||||
|
||||
try:
|
||||
# first part up to space is always the command name (i.e.: set, move)
|
||||
cmd, arg = command.split(' ', 1)
|
||||
|
||||
if cmd not in METHOD_MAP:
|
||||
ret['error'] = 'Command {0} is not supported (yet)'.format(cmd)
|
||||
return ret
|
||||
|
||||
method = METHOD_MAP[cmd]
|
||||
nargs = arg_map[method]
|
||||
|
||||
parts = salt.utils.shlex_split(arg)
|
||||
|
||||
if len(parts) not in nargs:
|
||||
err = '{0} takes {1} args: {2}'.format(method, nargs, parts)
|
||||
raise ValueError(err)
|
||||
@ -177,6 +193,9 @@ def execute(context=None, lens=None, commands=()):
|
||||
args = {'path': path}
|
||||
except ValueError as err:
|
||||
log.error(str(err))
|
||||
# if command.split fails arg will not be set
|
||||
if 'arg' not in locals():
|
||||
arg = command
|
||||
ret['error'] = 'Invalid formatted command, ' \
|
||||
'see debug log for details: {0}'.format(arg)
|
||||
return ret
|
||||
|
@ -32,16 +32,79 @@ from __future__ import absolute_import
|
||||
# Import python libs
|
||||
import re
|
||||
import os.path
|
||||
import logging
|
||||
import difflib
|
||||
|
||||
# Import Salt libs
|
||||
import salt.utils
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return 'augeas' if 'augeas.execute' in __salt__ else False
|
||||
|
||||
|
||||
def _workout_filename(filename):
|
||||
'''
|
||||
Recursively workout the file name from an augeas change
|
||||
'''
|
||||
if os.path.isfile(filename) or filename == '/':
|
||||
if filename == '/':
|
||||
filename = None
|
||||
return filename
|
||||
else:
|
||||
return _workout_filename(os.path.dirname(filename))
|
||||
|
||||
|
||||
def _check_filepath(changes):
|
||||
'''
|
||||
Ensure all changes are fully qualified and affect only one file.
|
||||
This ensures that the diff output works and a state change is not
|
||||
incorrectly reported.
|
||||
'''
|
||||
filename = None
|
||||
for change_ in changes:
|
||||
try:
|
||||
cmd, arg = change_.split(' ', 1)
|
||||
|
||||
if cmd not in __salt__['augeas.method_map']():
|
||||
error = 'Command {0} is not supported (yet)'.format(cmd)
|
||||
raise ValueError(error)
|
||||
method = __salt__['augeas.method_map']()[cmd]
|
||||
parts = salt.utils.shlex_split(arg)
|
||||
if method in ['set', 'setm', 'move', 'remove']:
|
||||
filename_ = parts[0]
|
||||
else:
|
||||
_, _, filename_ = parts
|
||||
if not filename_.startswith('/files'):
|
||||
error = 'Changes should be prefixed with ' \
|
||||
'/files if no context is provided,' \
|
||||
' change: {0}'.format(change_)
|
||||
raise ValueError(error)
|
||||
filename_ = re.sub('^/files|/$', '', filename_)
|
||||
if filename is not None:
|
||||
if filename != filename_:
|
||||
error = 'Changes should be made to one ' \
|
||||
'file at a time, detected changes ' \
|
||||
'to {0} and {1}'.format(filename, filename_)
|
||||
raise ValueError(error)
|
||||
filename = filename_
|
||||
except (ValueError, IndexError) as err:
|
||||
log.error(str(err))
|
||||
if 'error' not in locals():
|
||||
error = 'Invalid formatted command, ' \
|
||||
'see debug log for details: {0}' \
|
||||
.format(change_)
|
||||
else:
|
||||
error = str(err)
|
||||
raise ValueError(error)
|
||||
|
||||
filename = _workout_filename(filename)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def change(name, context=None, changes=None, lens=None, **kwargs):
|
||||
'''
|
||||
.. versionadded:: 2014.7.0
|
||||
@ -173,6 +236,16 @@ def change(name, context=None, changes=None, lens=None, **kwargs):
|
||||
ret['comment'] = '\'changes\' must be specified as a list'
|
||||
return ret
|
||||
|
||||
filename = None
|
||||
if context is None:
|
||||
try:
|
||||
filename = _check_filepath(changes)
|
||||
except ValueError as err:
|
||||
ret['comment'] = 'Error: {0}'.format(str(err))
|
||||
return ret
|
||||
else:
|
||||
filename = re.sub('^/files|/$', '', context)
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Executing commands'
|
||||
@ -182,8 +255,7 @@ def change(name, context=None, changes=None, lens=None, **kwargs):
|
||||
return ret
|
||||
|
||||
old_file = []
|
||||
if context:
|
||||
filename = re.sub('^/files|/$', '', context)
|
||||
if filename is not None:
|
||||
if os.path.isfile(filename):
|
||||
with salt.utils.fopen(filename, 'r') as file_:
|
||||
old_file = file_.readlines()
|
||||
|
@ -1,13 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
:codeauthor: :email:`Jayesh Kariya <jayeshk@saltstack.com>`
|
||||
:codeauthor: :email:`Andrew Colin Kissa <andrew@topdog.za.net>`
|
||||
'''
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from salttesting import skipIf, TestCase
|
||||
from salttesting.mock import (
|
||||
mock_open,
|
||||
NO_MOCK,
|
||||
NO_MOCK_REASON,
|
||||
MagicMock,
|
||||
@ -21,9 +25,6 @@ ensure_in_syspath('../../')
|
||||
# Import Salt Libs
|
||||
from salt.states import augeas
|
||||
|
||||
augeas.__opts__ = {}
|
||||
augeas.__salt__ = {}
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class AugeasTestCase(TestCase):
|
||||
@ -31,45 +32,204 @@ class AugeasTestCase(TestCase):
|
||||
Test cases for salt.states.augeas
|
||||
'''
|
||||
# 'change' function tests: 1
|
||||
def setUp(self):
|
||||
augeas.__opts__ = {}
|
||||
augeas.__salt__ = {}
|
||||
self.name = 'zabbix'
|
||||
self.context = '/files/etc/services'
|
||||
self.changes = ['ins service-name after service-name[last()]',
|
||||
'set service-name[last()] zabbix-agent']
|
||||
self.fp_changes = [
|
||||
'ins service-name after /files/etc/services/service-name[last()]',
|
||||
'set /files/etc/services/service-name[last()] zabbix-agent']
|
||||
self.ret = {'name': self.name,
|
||||
'result': False,
|
||||
'changes': {},
|
||||
'comment': ''}
|
||||
method_map = {
|
||||
'set': 'set',
|
||||
'setm': 'setm',
|
||||
'mv': 'move',
|
||||
'move': 'move',
|
||||
'ins': 'insert',
|
||||
'insert': 'insert',
|
||||
'rm': 'remove',
|
||||
'remove': 'remove',
|
||||
}
|
||||
self.mock_method_map = MagicMock(return_value=method_map)
|
||||
|
||||
def test_change(self):
|
||||
def test_change_non_list_changes(self):
|
||||
'''
|
||||
Test to issue changes to Augeas, optionally for a specific context,
|
||||
with a specific lens.
|
||||
Test if none list changes handled correctly
|
||||
'''
|
||||
name = 'zabbix'
|
||||
context = '/files/etc/services'
|
||||
changes = ['ins service-name after service-name[last()]',
|
||||
'set service-name[last()] zabbix-agent']
|
||||
changes_ret = {'updates': changes}
|
||||
|
||||
ret = {'name': name,
|
||||
'result': False,
|
||||
'changes': {},
|
||||
'comment': ''}
|
||||
|
||||
comt = ('\'changes\' must be specified as a list')
|
||||
ret.update({'comment': comt})
|
||||
self.assertDictEqual(augeas.change(name), ret)
|
||||
self.ret.update({'comment': comt})
|
||||
|
||||
self.assertDictEqual(augeas.change(self.name), self.ret)
|
||||
|
||||
def test_change_in_test_mode(self):
|
||||
'''
|
||||
Test test mode handling
|
||||
'''
|
||||
comt = ('Executing commands in file "/files/etc/services":\n'
|
||||
'ins service-name after service-name[last()]'
|
||||
'\nset service-name[last()] zabbix-agent')
|
||||
ret.update({'comment': comt, 'result': None})
|
||||
self.ret.update({'comment': comt, 'result': None})
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': True}):
|
||||
self.assertDictEqual(augeas.change(name, context, changes), ret)
|
||||
self.assertDictEqual(
|
||||
augeas.change(self.name, self.context, self.changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_no_context_without_full_path(self):
|
||||
'''
|
||||
Test handling of no context without full path
|
||||
'''
|
||||
comt = 'Error: Changes should be prefixed with /files if no ' \
|
||||
'context is provided, change: {0}'\
|
||||
.format(self.changes[0])
|
||||
self.ret.update({'comment': comt, 'result': False})
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock = MagicMock(return_value={'retval': False, 'error': 'error'})
|
||||
with patch.dict(augeas.__salt__, {'augeas.execute': mock}):
|
||||
ret.update({'comment': 'Error: error', 'result': False})
|
||||
self.assertDictEqual(augeas.change(name, changes=changes), ret)
|
||||
mock_dict_ = {'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
self.assertDictEqual(
|
||||
augeas.change(self.name, changes=self.changes),
|
||||
self.ret)
|
||||
|
||||
mock = MagicMock(return_value={'retval': True})
|
||||
with patch.dict(augeas.__salt__, {'augeas.execute': mock}):
|
||||
ret.update({'comment': 'Changes have been saved',
|
||||
'result': True, 'changes': changes_ret})
|
||||
self.assertDictEqual(augeas.change(name, changes=changes), ret)
|
||||
def test_change_no_context_with_full_path_fail(self):
|
||||
'''
|
||||
Test handling of no context with full path with execute fail
|
||||
'''
|
||||
self.ret.update({'comment': 'Error: error', 'result': False})
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(
|
||||
return_value=dict(retval=False, error='error'))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
self.assertDictEqual(
|
||||
augeas.change(self.name, changes=self.fp_changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_no_context_with_full_path_pass(self):
|
||||
'''
|
||||
Test handling of no context with full path with execute pass
|
||||
'''
|
||||
self.ret.update(dict(comment='Changes have been saved',
|
||||
result=True,
|
||||
changes={'diff': '+ zabbix-agent'}))
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(return_value=dict(retval=True))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
mock_filename = MagicMock(return_value='/etc/services')
|
||||
with patch.object(augeas, '_workout_filename', mock_filename):
|
||||
with patch('salt.utils.fopen', MagicMock(mock_open)):
|
||||
mock_diff = MagicMock(return_value=['+ zabbix-agent'])
|
||||
with patch('difflib.unified_diff', mock_diff):
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
changes=self.fp_changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_no_context_without_full_path_invalid_cmd(self):
|
||||
'''
|
||||
Test handling of invalid commands when no context supplied
|
||||
'''
|
||||
self.ret.update(dict(comment='Error: Command det is not supported (yet)',
|
||||
result=False))
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(return_value=dict(retval=True))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
changes = ['det service-name[last()] zabbix-agent']
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
changes=changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_no_context_without_full_path_invalid_change(self):
|
||||
'''
|
||||
Test handling of invalid change when no context supplied
|
||||
'''
|
||||
comt = ('Error: Invalid formatted command, see '
|
||||
'debug log for details: require')
|
||||
self.ret.update(dict(comment=comt,
|
||||
result=False))
|
||||
changes = ['require']
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(return_value=dict(retval=True))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
changes=changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_no_context_with_full_path_multiple_files(self):
|
||||
'''
|
||||
Test handling of different paths with no context supplied
|
||||
'''
|
||||
changes = ['set /files/etc/hosts/service-name test',
|
||||
'set /files/etc/services/service-name test']
|
||||
filename = '/etc/hosts/service-name'
|
||||
filename_ = '/etc/services/service-name'
|
||||
comt = 'Error: Changes should be made to one file at a time, ' \
|
||||
'detected changes to {0} and {1}'.format(filename, filename_)
|
||||
self.ret.update(dict(comment=comt,
|
||||
result=False))
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(return_value=dict(retval=True))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
changes=changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_with_context_without_full_path_fail(self):
|
||||
'''
|
||||
Test handling of context without full path fails
|
||||
'''
|
||||
self.ret.update(dict(comment='Error: error', result=False))
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(
|
||||
return_value=dict(retval=False, error='error'))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
with patch('salt.utils.fopen', MagicMock(mock_open)):
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
context=self.context,
|
||||
changes=self.changes),
|
||||
self.ret)
|
||||
|
||||
def test_change_with_context_without_old_file(self):
|
||||
'''
|
||||
Test handling of context without oldfile pass
|
||||
'''
|
||||
self.ret.update(dict(comment='Changes have been saved',
|
||||
result=True,
|
||||
changes={'updates': self.changes}))
|
||||
|
||||
with patch.dict(augeas.__opts__, {'test': False}):
|
||||
mock_execute = MagicMock(return_value=dict(retval=True))
|
||||
mock_dict_ = {'augeas.execute': mock_execute,
|
||||
'augeas.method_map': self.mock_method_map}
|
||||
with patch.dict(augeas.__salt__, mock_dict_):
|
||||
mock_isfile = MagicMock(return_value=False)
|
||||
with patch.object(os.path, 'isfile', mock_isfile):
|
||||
self.assertDictEqual(augeas.change(self.name,
|
||||
context=self.context,
|
||||
changes=self.changes),
|
||||
self.ret)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
Loading…
Reference in New Issue
Block a user