More refactoring for better organization of ZFS hook (#261).

This commit is contained in:
Dan Helfman 2024-11-21 22:09:18 -08:00
parent 87f36caf8d
commit 06e0f98fd8

View File

@ -32,16 +32,17 @@ def get_datasets_to_backup(zfs_command, source_directories):
Return the result as a sequence of (dataset name, mount point) pairs.
'''
list_command = (
zfs_command,
'list',
'-H',
'-t',
'filesystem',
'-o',
f'name,mountpoint,{BORGMATIC_USER_PROPERTY}',
list_output = borgmatic.execute.execute_command_and_capture_output(
(
zfs_command,
'list',
'-H',
'-t',
'filesystem',
'-o',
f'name,mountpoint,{BORGMATIC_USER_PROPERTY}',
)
)
list_output = borgmatic.execute.execute_command_and_capture_output(list_command)
source_directories_set = set(source_directories)
return tuple(
@ -52,6 +53,30 @@ def get_datasets_to_backup(zfs_command, source_directories):
)
def get_all_datasets(zfs_command):
'''
Given a ZFS command to run, return all ZFS datasets as a sequence of (dataset name, mount point)
pairs.
'''
list_output = borgmatic.execute.execute_command_and_capture_output(
(
zfs_command,
'list',
'-H',
'-t',
'filesystem',
'-o',
f'name,mountpoint',
)
)
return tuple(
(dataset_name, mount_point)
for line in list_output.splitlines()
for (dataset_name, mount_point) in (line.rstrip().split('\t'),)
)
def snapshot_dataset(zfs_command, full_snapshot_name):
'''
Given a ZFS command to run and a snapshot name of the form "dataset@snapshot", create a new ZFS
@ -148,6 +173,57 @@ def dump_data_sources(
return []
def unmount_snapshot(umount_command, snapshot_mount_path):
'''
Given a umount command to run and the mount path of a snapshot, unmount it.
'''
borgmatic.execute.execute_command(
(
umount_command,
snapshot_mount_path,
),
output_log_level=logging.DEBUG,
)
def destroy_snapshot(zfs_command, full_snapshot_name):
'''
Given a ZFS command to run and the name of a snapshot in the form "dataset@snapshot", destroy
it.
'''
borgmatic.execute.execute_command(
(
zfs_command,
'destroy',
'-r',
full_snapshot_name,
),
output_log_level=logging.DEBUG,
)
def get_all_snapshots(zfs_command):
'''
Given a ZFS command to run, return all ZFS snapshots as a sequence of full snapshot names of the
form "dataset@snapshot".
'''
list_output = borgmatic.execute.execute_command_and_capture_output(
(
zfs_command,
'list',
'-H',
'-t',
'snapshot',
'-o',
'name',
)
)
return tuple(
line.rstrip()
for line in list_output.splitlines()
)
def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Given a ZFS configuration dict, a configuration dict, a log prefix, the borgmatic runtime
@ -159,17 +235,9 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
# Unmount snapshots.
zfs_command = hook_config.get('zfs_command', 'zfs')
list_datasets_command = (
zfs_command,
'list',
'-H',
'-o',
'name,mountpoint',
)
try:
list_datasets_output = borgmatic.execute.execute_command_and_capture_output(
list_datasets_command
)
datasets = get_all_datasets(zfs_command)
except FileNotFoundError:
logger.debug(f'{log_prefix}: Could not find "{zfs_command}" command')
return
@ -177,11 +245,6 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
logger.debug(f'{log_prefix}: {error}')
return
mount_points = tuple(
mount_point
for line in list_datasets_output.splitlines()
for (dataset_name, mount_point) in (line.rstrip().split('\t'),)
)
snapshots_glob = os.path.join(
borgmatic.config.paths.replace_temporary_subdirectory_with_glob(
os.path.normpath(borgmatic_runtime_directory)
@ -191,6 +254,7 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
logger.debug(
f'{log_prefix}: Looking for snapshots to remove in {snapshots_glob}{dry_run_label}'
)
umount_command = hook_config.get('umount_command', 'umount')
for snapshots_directory in glob.glob(snapshots_glob):
if not os.path.isdir(snapshots_directory):
@ -202,37 +266,19 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
# mounted is tough to do in a cross-platform way.
shutil.rmtree(snapshots_directory, ignore_errors=True)
for mount_point in mount_points:
snapshot_path = os.path.join(snapshots_directory, mount_point.lstrip(os.path.sep))
logger.debug(f'{log_prefix}: Unmounting ZFS snapshot at {snapshot_path}{dry_run_label}')
for (_, mount_point) in datasets:
snapshot_mount_path = os.path.join(snapshots_directory, mount_point.lstrip(os.path.sep))
logger.debug(f'{log_prefix}: Unmounting ZFS snapshot at {snapshot_mount_path}{dry_run_label}')
if not dry_run:
borgmatic.execute.execute_command(
(
hook_config.get('umount_command', 'umount'),
snapshot_path,
),
output_log_level=logging.DEBUG,
)
unmount_snapshot(umount_command, snapshot_mount_path)
shutil.rmtree(snapshots_directory)
# Destroy snapshots.
list_snapshots_command = (
zfs_command,
'list',
'-H',
'-t',
'snapshot',
'-o',
'name',
)
list_snapshots_output = borgmatic.execute.execute_command_and_capture_output(
list_snapshots_command
)
full_snapshot_names = get_all_snapshots(zfs_command)
for line in list_snapshots_output.splitlines():
full_snapshot_name = line.rstrip()
for full_snapshot_name in full_snapshot_names:
logger.debug(f'{log_prefix}: Destroying ZFS snapshot {full_snapshot_name}{dry_run_label}')
# Only destroy snapshots that borgmatic actually created!
@ -240,15 +286,7 @@ def remove_data_source_dumps(hook_config, config, log_prefix, borgmatic_runtime_
continue
if not dry_run:
borgmatic.execute.execute_command(
(
zfs_command,
'destroy',
'-r',
full_snapshot_name,
),
output_log_level=logging.DEBUG,
)
destroy_snapshot(zfs_command, full_snapshot_name)
def make_data_source_dump_patterns(hook_config, config, log_prefix, name=None): # pragma: no cover