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:
Mike Place 2017-01-04 08:32:29 -07:00 committed by GitHub
commit 53449c89a5
3 changed files with 69 additions and 26 deletions

View file

@ -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

View file

@ -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:

View file

@ -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,