diff --git a/focker/focker.py b/focker/focker.py index 7b3af25..ebf6870 100644 --- a/focker/focker.py +++ b/focker/focker.py @@ -24,7 +24,9 @@ from .volume import command_volume_create, \ command_volume_untag, \ command_volume_remove, \ command_volume_set, \ - command_volume_get + command_volume_get, \ + command_volume_protect, \ + command_volume_unprotect import sys from .zfs import zfs_init from .jail import command_jail_create, \ @@ -209,6 +211,14 @@ def create_parser(): parser.add_argument('reference', type=str) parser.add_argument('properties', type=str, nargs=argparse.REMAINDER) + parser = ListForwarder([subparsers.add_parser(cmd) for cmd in ['protect']]) + parser.set_defaults(func=command_volume_protect) + parser.add_argument('references', type=str, nargs='+') + + parser = ListForwarder([subparsers.add_parser(cmd) for cmd in ['unprotect']]) + parser.set_defaults(func=command_volume_unprotect) + parser.add_argument('references', type=str, nargs='+') + # compose subparsers = ListForwarder([ subparsers_top.add_parser(cmd).add_subparsers(dest='L2_command') \ for cmd in ['compose', 'comp', 'c'] ]) diff --git a/focker/volume.py b/focker/volume.py index c9908af..7912a1a 100644 --- a/focker/volume.py +++ b/focker/volume.py @@ -51,7 +51,7 @@ def command_volume_remove(args): try: name, _ = zfs_find(ref, focker_type='volume') print('Removing:', name) - zfs_run(['zfs', 'destroy', '-r', '-f', name]) + zfs_destroy(name) except: if not args.force: raise @@ -71,3 +71,17 @@ def command_volume_get(args): res = zfs_parse_output(['zfs', 'get', '-H', ','.join(args.properties), name]) res = [ [ args.properties[i], a[2] ] for i, a in enumerate(res) ] print(tabulate(res, headers=['Property', 'Value'])) + + +def command_volume_protect(args): + for ref in args.references: + name, _ = zfs_find(ref, focker_type='volume') + print('Protecting:', name) + zfs_protect(name) + + +def command_volume_unprotect(args): + for ref in args.references: + name, _ = zfs_find(ref, focker_type='volume') + print('Unprotecting:', name) + zfs_unprotect(name) diff --git a/focker/zfs.py b/focker/zfs.py index 6d037e9..8d22536 100644 --- a/focker/zfs.py +++ b/focker/zfs.py @@ -83,7 +83,7 @@ def zfs_prune(focker_type='image'): again = True while again: again = False - lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,focker:tags,origin,name', '-H', '-r', poolname + '/focker/' + focker_type + 's']) + lst = zfs_parse_output(['zfs', 'list', '-o', 'focker:sha256,focker:tags,origin,name,focker:protect', '-H', '-r', poolname + '/focker/' + focker_type + 's']) used = set() for r in lst: if r[2] == '-': @@ -92,10 +92,29 @@ def zfs_prune(focker_type='image'): for r in lst: if r[0] == '-' or r[1] != '-': continue - if r[3] not in used: - print('Removing:', r[3]) - zfs_run(['zfs', 'destroy', '-r', '-f', r[3]]) - again = True + if r[3] in used: + continue + if r[4] != '-': + print('%s is protected against removal' % r[3]) + continue + print('Removing:', r[3]) + zfs_run(['zfs', 'destroy', '-r', '-f', r[3]]) + again = True + + +def zfs_destroy(name): + lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:protect', name]) + if lst[0][2] != '-': + raise RuntimeError('%s is protected against removal' % name) + zfs_run(['zfs', 'destroy', '-r', '-f', name]) + + +def zfs_protect(name): + zfs_run(['zfs', 'set', 'focker:protect=on', name]) + + +def zfs_unprotect(name): + zfs_run(['zfs', 'inherit', '-r', 'focker:protect', name]) def zfs_clone(name, target_name): diff --git a/tests/test_volume.py b/tests/test_volume.py index 9ec7ad6..73b048e 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -7,13 +7,19 @@ from focker.volume import command_volume_create, \ command_volume_untag, \ command_volume_remove, \ command_volume_set, \ - command_volume_get + command_volume_get, \ + command_volume_protect, \ + command_volume_unprotect from focker.zfs import zfs_find, \ zfs_mountpoint, \ zfs_exists, \ - zfs_parse_output + zfs_parse_output, \ + zfs_destroy, \ + zfs_prune, \ + zfs_run import os import focker.volume +import focker.zfs def test_command_volume_create(): @@ -162,3 +168,52 @@ def test_command_volume_get(monkeypatch): assert headers == [ 'Property', 'Value' ] # assert lst == ['on', '1G'] subprocess.check_output(['focker', 'volume', 'remove', 'test-command-volume-get']) + + +def test_command_volume_protect(monkeypatch): + subprocess.check_output(['focker', 'volume', 'remove', '--force', 'test-command-volume-protect']) + subprocess.check_output(['focker', 'volume', 'create', '-t', 'test-command-volume-protect']) + args = lambda: 0 + args.references = ['test-command-volume-protect'] + command_volume_protect(args) + name, sha256 = zfs_find('test-command-volume-protect', focker_type='volume') + mountpoint = zfs_mountpoint(name) + lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:protect', name]) + assert len(lst) == 1 + assert lst[0][2] == 'on' + with pytest.raises(RuntimeError): + zfs_destroy(name) + subprocess.check_output(['focker', 'volume', 'untag', 'test-command-volume-protect']) + lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:tags', name]) + assert len(lst) == 1 + assert lst[0][2] == '-' + n_called = 0 + def fake_run(*args, **kwargs): + nonlocal n_called + n_called += 1 + return zfs_run(*args, **kwargs) + monkeypatch.setattr(focker.zfs, 'zfs_run', fake_run) + zfs_prune(focker_type='volume') + assert not n_called == 1 + with pytest.raises(subprocess.CalledProcessError): + subprocess.check_output(['focker', 'volume', 'remove', sha256]) + subprocess.check_output(['zfs', 'destroy', '-r', '-f', name]) + assert not zfs_exists(name) + assert not os.path.exists(mountpoint) + + +def test_command_volume_unprotect(): + subprocess.check_output(['focker', 'volume', 'remove', '--force', 'test-command-volume-unprotect']) + subprocess.check_output(['focker', 'volume', 'create', '-t', 'test-command-volume-unprotect']) + subprocess.check_output(['focker', 'volume', 'protect', 'test-command-volume-unprotect']) + name, _ = zfs_find('test-command-volume-unprotect', focker_type='volume') + lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:protect', name]) + assert len(lst) == 1 + assert lst[0][2] == 'on' + args = lambda: 0 + args.references = ['test-command-volume-unprotect'] + command_volume_unprotect(args) + lst = zfs_parse_output(['zfs', 'get', '-H', 'focker:protect', name]) + assert len(lst) == 1 + assert lst[0][2] == '-' + subprocess.check_output(['focker', 'volume', 'remove', 'test-command-volume-unprotect'])