mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #38549 from meaksh/2016.11-snapper-multiple-subvolumen-support
Adding multiple SUBVOLUME support and some fixes to the Snapper module
This commit is contained in:
commit
53449c89a5
3 changed files with 69 additions and 26 deletions
|
@ -483,8 +483,12 @@ def status(config='root', num_pre=None, num_post=None):
|
|||
snapper.CreateComparison(config, int(pre), int(post))
|
||||
files = snapper.GetFiles(config, int(pre), int(post))
|
||||
status_ret = {}
|
||||
SUBVOLUME = list_configs()[config]['SUBVOLUME']
|
||||
for file in files:
|
||||
status_ret[file[0]] = {'status': status_to_string(file[1])}
|
||||
# In case of SUBVOLUME is included in filepath we remove it
|
||||
# to prevent from filepath starting with double '/'
|
||||
_filepath = file[0][len(SUBVOLUME):] if file[0].startswith(SUBVOLUME) else file[0]
|
||||
status_ret[os.path.normpath(SUBVOLUME + _filepath)] = {'status': status_to_string(file[1])}
|
||||
return status_ret
|
||||
except dbus.DBusException as exc:
|
||||
raise CommandExecutionError(
|
||||
|
@ -546,14 +550,19 @@ def undo(config='root', files=None, num_pre=None, num_post=None):
|
|||
'Given file list contains files that are not present'
|
||||
'in the changed filelist: {0}'.format(changed - requested))
|
||||
|
||||
cmdret = __salt__['cmd.run']('snapper undochange {0}..{1} {2}'.format(
|
||||
pre, post, ' '.join(requested)))
|
||||
components = cmdret.split(' ')
|
||||
ret = {}
|
||||
for comp in components:
|
||||
key, val = comp.split(':')
|
||||
ret[key] = val
|
||||
return ret
|
||||
cmdret = __salt__['cmd.run']('snapper -c {0} undochange {1}..{2} {3}'.format(
|
||||
config, pre, post, ' '.join(requested)))
|
||||
|
||||
try:
|
||||
components = cmdret.split(' ')
|
||||
ret = {}
|
||||
for comp in components:
|
||||
key, val = comp.split(':')
|
||||
ret[key] = val
|
||||
return ret
|
||||
except ValueError as exc:
|
||||
raise CommandExecutionError(
|
||||
'Error while processing Snapper response: {0}'.format(cmdret))
|
||||
|
||||
|
||||
def _get_jid_snapshots(jid, config='root'):
|
||||
|
@ -627,13 +636,20 @@ def diff(config='root', filename=None, num_pre=None, num_post=None):
|
|||
if filename:
|
||||
files = [filename] if filename in files else []
|
||||
|
||||
pre_mount = snapper.MountSnapshot(config, pre, False) if pre else ""
|
||||
post_mount = snapper.MountSnapshot(config, post, False) if post else ""
|
||||
SUBVOLUME = list_configs()[config]['SUBVOLUME']
|
||||
pre_mount = snapper.MountSnapshot(config, pre, False) if pre else SUBVOLUME
|
||||
post_mount = snapper.MountSnapshot(config, post, False) if post else SUBVOLUME
|
||||
|
||||
files_diff = dict()
|
||||
for filepath in [filepath for filepath in files if not os.path.isdir(filepath)]:
|
||||
pre_file = pre_mount + filepath
|
||||
post_file = post_mount + filepath
|
||||
|
||||
_filepath = filepath
|
||||
if filepath.startswith(SUBVOLUME):
|
||||
_filepath = filepath[len(SUBVOLUME):]
|
||||
|
||||
# Just in case, removing posible double '/' from the final file paths
|
||||
pre_file = os.path.normpath(pre_mount + "/" + _filepath).replace("//", "/")
|
||||
post_file = os.path.normpath(post_mount + "/" + _filepath).replace("//", "/")
|
||||
|
||||
if os.path.isfile(pre_file):
|
||||
pre_file_exists = True
|
||||
|
|
|
@ -25,7 +25,7 @@ The snapper state module allows you to manage state implicitly, in addition
|
|||
to explicit rules, in order to define a baseline and iterate with explicit
|
||||
rules as they show that they work in production.
|
||||
|
||||
The workflow is: once you have a workin and audited system, you would create
|
||||
The workflow is: once you have a working and audited system, you would create
|
||||
your baseline snapshot (eg. with ``salt tgt snapper.create_snapshot``) and
|
||||
define in your state this baseline using the identifier of the snapshot
|
||||
(in this case: 20):
|
||||
|
@ -35,10 +35,20 @@ define in your state this baseline using the identifier of the snapshot
|
|||
my_baseline:
|
||||
snapper.baseline_snapshot:
|
||||
- number: 20
|
||||
- include_diff: False
|
||||
- ignore:
|
||||
- /var/log
|
||||
- /var/cache
|
||||
|
||||
Baseline snapshots can be also referenced by tag. Most recent baseline snapshot
|
||||
is used in case of multiple snapshots with the same tag:
|
||||
|
||||
my_baseline_external_storage:
|
||||
snapper.baseline_snapshot:
|
||||
- tag: my_custom_baseline_tag
|
||||
- config: external
|
||||
- ignore:
|
||||
- /mnt/tmp_files/
|
||||
|
||||
If you have this state, and you haven't done changes to the system since the
|
||||
snapshot, and you add a user, the state will show you the changes (including
|
||||
|
@ -109,25 +119,39 @@ def __virtual__():
|
|||
return 'snapper' if 'snapper.diff' in __salt__ else False
|
||||
|
||||
|
||||
def _get_baseline_from_tag(tag):
|
||||
def _get_baseline_from_tag(config, tag):
|
||||
'''
|
||||
Returns the last created baseline snapshot marked with `tag`
|
||||
'''
|
||||
last_snapshot = None
|
||||
for snapshot in __salt__['snapper.list_snapshots']():
|
||||
for snapshot in __salt__['snapper.list_snapshots'](config):
|
||||
if tag == snapshot['userdata'].get("baseline_tag"):
|
||||
if not last_snapshot or last_snapshot['timestamp'] < snapshot['timestamp']:
|
||||
last_snapshot = snapshot
|
||||
return last_snapshot
|
||||
|
||||
|
||||
def baseline_snapshot(name, number=None, tag=None, config='root', ignore=None):
|
||||
def baseline_snapshot(name, number=None, tag=None, include_diff=True, config='root', ignore=None):
|
||||
'''
|
||||
Enforces that no file is modified comparing against a previously
|
||||
defined snapshot identified by number.
|
||||
|
||||
number
|
||||
Number of selected baseline snapshot.
|
||||
|
||||
tag
|
||||
Tag of the selected baseline snapshot. Most recent baseline baseline
|
||||
snapshot is used in case of multiple snapshots with the same tag.
|
||||
(`tag` and `number` cannot be used at the same time)
|
||||
|
||||
include_diff
|
||||
Include a diff in the response (Default: True)
|
||||
|
||||
config
|
||||
Snapper config name (Default: root)
|
||||
|
||||
ignore
|
||||
List of files to ignore
|
||||
List of files to ignore. (Default: None)
|
||||
'''
|
||||
if not ignore:
|
||||
ignore = []
|
||||
|
@ -148,7 +172,7 @@ def baseline_snapshot(name, number=None, tag=None, config='root', ignore=None):
|
|||
return ret
|
||||
|
||||
if tag:
|
||||
snapshot = _get_baseline_from_tag(tag)
|
||||
snapshot = _get_baseline_from_tag(config, tag)
|
||||
if not snapshot:
|
||||
ret.update({'result': False,
|
||||
'comment': 'Baseline tag "{0}" not found'.format(tag)})
|
||||
|
@ -156,7 +180,7 @@ def baseline_snapshot(name, number=None, tag=None, config='root', ignore=None):
|
|||
number = snapshot['id']
|
||||
|
||||
status = __salt__['snapper.status'](
|
||||
config, num_pre=number, num_post=0)
|
||||
config, num_pre=0, num_post=number)
|
||||
|
||||
for target in ignore:
|
||||
if os.path.isfile(target):
|
||||
|
@ -166,18 +190,17 @@ def baseline_snapshot(name, number=None, tag=None, config='root', ignore=None):
|
|||
status.pop(target_file, None)
|
||||
|
||||
for file in status:
|
||||
status[file]['actions'] = status[file].pop("status")
|
||||
|
||||
# Only include diff for modified files
|
||||
if "modified" in status[file]['actions']:
|
||||
if "modified" in status[file]["status"] and include_diff:
|
||||
status[file].pop("status")
|
||||
status[file].update(__salt__['snapper.diff'](config,
|
||||
num_pre=0,
|
||||
num_post=number,
|
||||
filename=file)[file])
|
||||
filename=file).get(file, {}))
|
||||
|
||||
if __opts__['test'] and status:
|
||||
ret['pchanges'] = ret["changes"]
|
||||
ret['changes'] = {}
|
||||
ret['pchanges'] = status
|
||||
ret['changes'] = ret['pchanges']
|
||||
ret['comment'] = "{0} files changes are set to be undone".format(len(status.keys()))
|
||||
ret['result'] = None
|
||||
elif __opts__['test'] and not status:
|
||||
|
|
|
@ -234,6 +234,7 @@ class SnapperTestCase(TestCase):
|
|||
@patch('salt.modules.snapper._get_num_interval', MagicMock(return_value=(42, 43)))
|
||||
@patch('salt.modules.snapper.snapper.GetComparison', MagicMock())
|
||||
@patch('salt.modules.snapper.snapper.GetFiles', MagicMock(return_value=DBUS_RET['GetFiles']))
|
||||
@patch('salt.modules.snapper.snapper.ListConfigs', MagicMock(return_value=DBUS_RET['ListConfigs']))
|
||||
def test_status(self):
|
||||
if six.PY3:
|
||||
self.assertCountEqual(snapper.status(), MODULE_RET['GETFILES'])
|
||||
|
@ -288,6 +289,7 @@ class SnapperTestCase(TestCase):
|
|||
@patch('salt.modules.snapper._is_text_file', MagicMock(return_value=True))
|
||||
@patch('os.path.isfile', MagicMock(side_effect=[False, True]))
|
||||
@patch('salt.utils.fopen', mock_open(read_data=FILE_CONTENT["/tmp/foo2"]['post']))
|
||||
@patch('salt.modules.snapper.snapper.ListConfigs', MagicMock(return_value=DBUS_RET['ListConfigs']))
|
||||
def test_diff_text_file(self):
|
||||
if sys.version_info < (2, 7):
|
||||
self.assertEqual(snapper.diff(), {"/tmp/foo2": MODULE_RET['DIFF']['/tmp/foo26']})
|
||||
|
@ -302,6 +304,7 @@ class SnapperTestCase(TestCase):
|
|||
@patch('salt.modules.snapper._is_text_file', MagicMock(return_value=True))
|
||||
@patch('os.path.isfile', MagicMock(side_effect=[True, True, False, True]))
|
||||
@patch('os.path.isdir', MagicMock(return_value=False))
|
||||
@patch('salt.modules.snapper.snapper.ListConfigs', MagicMock(return_value=DBUS_RET['ListConfigs']))
|
||||
@skipIf(sys.version_info < (2, 7), 'Python 2.7 required to compare diff properly')
|
||||
def test_diff_text_files(self):
|
||||
fopen_effect = [
|
||||
|
@ -331,6 +334,7 @@ class SnapperTestCase(TestCase):
|
|||
"f18f971f1517449208a66589085ddd3723f7f6cefb56c141e3d97ae49e1d87fa",
|
||||
])
|
||||
})
|
||||
@patch('salt.modules.snapper.snapper.ListConfigs', MagicMock(return_value=DBUS_RET['ListConfigs']))
|
||||
def test_diff_binary_files(self):
|
||||
fopen_effect = [
|
||||
mock_open(read_data="dummy binary").return_value,
|
||||
|
|
Loading…
Add table
Reference in a new issue