Fix for a runtime directory error when the configured patterns contain a global exclude (#1150).

This commit is contained in:
2025-10-22 11:10:42 -07:00
parent 44d63cac07
commit dcd567f4f0
25 changed files with 939 additions and 274 deletions

1
NEWS
View File

@@ -5,6 +5,7 @@
databases when dumping "all".
* #1150: Fix for a runtime directory error when the "create" action is used with the "--log-json"
flag.
* #1150: Fix for a runtime directory error when the configured patterns contain a global exclude.
* #1161: Fix a traceback (TypeError) in the "check" action with Python 3.14.
* Add documentation search.
* Change the URL of the local documentation development server to be more like the production URL.

View File

@@ -7,6 +7,7 @@ import os
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.data_source.config
logger = logging.getLogger(__name__)
@@ -58,20 +59,23 @@ def dump_data_sources(
manifest_file,
)
patterns.extend(
borgmatic.borg.pattern.Pattern(
config_path,
source=borgmatic.borg.pattern.Pattern_source.HOOK,
)
for config_path in config_paths
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'bootstrap'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,
),
)
for config_path in config_paths:
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
config_path,
source=borgmatic.borg.pattern.Pattern_source.HOOK,
),
)
return []

View File

@@ -10,6 +10,7 @@ import subprocess
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.execute
import borgmatic.hooks.data_source.config
import borgmatic.hooks.data_source.snapshot
logger = logging.getLogger(__name__)
@@ -330,14 +331,11 @@ def dump_data_sources(
for pattern in subvolume.contained_patterns:
snapshot_pattern = make_borg_snapshot_pattern(subvolume.path, pattern)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
# Attempt to update the pattern in place, since pattern order matters to Borg.
try:
patterns[patterns.index(pattern)] = snapshot_pattern
except ValueError:
patterns.append(snapshot_pattern)
patterns.append(make_snapshot_exclude_pattern(subvolume.path))
borgmatic.hooks.data_source.config.inject_pattern(
patterns, make_snapshot_exclude_pattern(subvolume.path)
)
return []

View File

@@ -3,6 +3,7 @@ import logging
import shutil
import subprocess
import borgmatic.borg.pattern
from borgmatic.execute import execute_command_and_capture_output
IS_A_HOOK = False
@@ -102,3 +103,65 @@ def get_ip_from_container(container):
raise ValueError(
f"Could not determine ip address for container '{container}'; running in host mode or userspace networking?"
)
def inject_pattern(patterns, data_source_pattern):
'''
Given a list of borgmatic.borg.pattern.Pattern instances representing the configured patterns,
insert the given data source pattern at the start of the list. The idea is that borgmatic is
injecting its own custom pattern specific to a data source hook into the user's configured
patterns so that the hook's data gets included in the backup.
As part of this injection, if the data source pattern is a root pattern, also insert an
"include" version of the given root pattern, in an attempt to preempt any of the user's
configured exclude patterns that may follow.
'''
if data_source_pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT:
patterns.insert(
0,
borgmatic.borg.pattern.Pattern(
path=data_source_pattern.path,
type=borgmatic.borg.pattern.Pattern_type.INCLUDE,
style=data_source_pattern.style,
device=data_source_pattern.device,
source=borgmatic.borg.pattern.Pattern_source.HOOK,
),
)
patterns.insert(0, data_source_pattern)
def replace_pattern(patterns, pattern_to_replace, data_source_pattern):
'''
Given a list of borgmatic.borg.pattern.Pattern instances representing the configured patterns,
replace the given pattern with the given data source pattern. The idea is that borgmatic is
replacing a configured pattern with its own modified pattern specific to a data source hook so
that the hook's data gets included in the backup.
As part of this replacement, if the data source pattern is a root pattern, also insert an
"include" version of the given root pattern right after the replaced pattern, in an attempt to
preempt any of the user's configured exclude patterns that may follow.
If the pattern to replace can't be found in the given patterns, then just inject the data source
pattern at the start of the list.
'''
try:
index = patterns.index(pattern_to_replace)
except ValueError:
inject_pattern(patterns, data_source_pattern)
return
patterns[index] = data_source_pattern
if data_source_pattern.type == borgmatic.borg.pattern.Pattern_type.ROOT:
patterns.insert(
index + 1,
borgmatic.borg.pattern.Pattern(
path=data_source_pattern.path,
type=borgmatic.borg.pattern.Pattern_type.INCLUDE,
style=data_source_pattern.style,
device=data_source_pattern.device,
source=borgmatic.borg.pattern.Pattern_source.HOOK,
),
)

View File

@@ -10,6 +10,7 @@ import subprocess
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.execute
import borgmatic.hooks.data_source.config
import borgmatic.hooks.data_source.snapshot
logger = logging.getLogger(__name__)
@@ -275,11 +276,7 @@ def dump_data_sources(
normalized_runtime_directory,
)
# Attempt to update the pattern in place, since pattern order matters to Borg.
try:
patterns[patterns.index(pattern)] = snapshot_pattern
except ValueError:
patterns.append(snapshot_pattern)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
return []

View File

@@ -7,6 +7,7 @@ import shlex
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.credential.parse
import borgmatic.hooks.data_source.config
from borgmatic.execute import (
execute_command,
execute_command_and_capture_output,
@@ -366,7 +367,8 @@ def dump_data_sources(
dump.write_data_source_dumps_metadata(
borgmatic_runtime_directory, 'mariadb_databases', dumps_metadata
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'mariadb_databases'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,

View File

@@ -5,6 +5,7 @@ import shlex
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.credential.parse
import borgmatic.hooks.data_source.config
from borgmatic.execute import execute_command, execute_command_with_processes
from borgmatic.hooks.data_source import config as database_config
from borgmatic.hooks.data_source import dump
@@ -100,7 +101,8 @@ def dump_data_sources(
dump.write_data_source_dumps_metadata(
borgmatic_runtime_directory, 'mongodb_databases', dumps_metadata
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'mongodb_databases'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,

View File

@@ -6,6 +6,7 @@ import shlex
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.credential.parse
import borgmatic.hooks.data_source.config
import borgmatic.hooks.data_source.mariadb
from borgmatic.execute import (
execute_command,
@@ -297,7 +298,8 @@ def dump_data_sources(
dump.write_data_source_dumps_metadata(
borgmatic_runtime_directory, 'mysql_databases', dumps_metadata
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'mysql_databases'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,

View File

@@ -8,6 +8,7 @@ import shlex
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.credential.parse
import borgmatic.hooks.data_source.config
from borgmatic.execute import (
execute_command,
execute_command_and_capture_output,
@@ -260,7 +261,8 @@ def dump_data_sources(
dump.write_data_source_dumps_metadata(
borgmatic_runtime_directory, 'postgresql_databases', dumps_metadata
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'postgresql_databases'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,

View File

@@ -4,6 +4,7 @@ import shlex
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.hooks.data_source.config
from borgmatic.execute import execute_command, execute_command_with_processes
from borgmatic.hooks.data_source import dump
@@ -106,7 +107,8 @@ def dump_data_sources(
dump.write_data_source_dumps_metadata(
borgmatic_runtime_directory, 'sqlite_databases', dumps_metadata
)
patterns.append(
borgmatic.hooks.data_source.config.inject_pattern(
patterns,
borgmatic.borg.pattern.Pattern(
os.path.join(borgmatic_runtime_directory, 'sqlite_databases'),
source=borgmatic.borg.pattern.Pattern_source.HOOK,

View File

@@ -9,6 +9,7 @@ import subprocess
import borgmatic.borg.pattern
import borgmatic.config.paths
import borgmatic.execute
import borgmatic.hooks.data_source.config
import borgmatic.hooks.data_source.snapshot
logger = logging.getLogger(__name__)
@@ -306,11 +307,7 @@ def dump_data_sources(
normalized_runtime_directory,
)
# Attempt to update the pattern in place, since pattern order matters to Borg.
try:
patterns[patterns.index(pattern)] = snapshot_pattern
except ValueError:
patterns.append(snapshot_pattern)
borgmatic.hooks.data_source.config.replace_pattern(patterns, pattern, snapshot_pattern)
return []

View File

@@ -0,0 +1,59 @@
from flexmock import flexmock
from borgmatic.borg.pattern import Pattern, Pattern_style, Pattern_type
from borgmatic.hooks.data_source import btrfs as module
def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')]
config = {'btrfs': {}}
flexmock(module).should_receive('get_subvolumes').and_return(
(
module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
),
)
flexmock(module.os).should_receive('getpid').and_return(1234)
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).once()
assert (
module.dump_data_sources(
hook_config=config['btrfs'],
config=config,
config_paths=('test.yaml',),
borgmatic_runtime_directory='/run/borgmatic',
patterns=patterns,
dry_run=False,
)
== []
)
assert patterns == [
Pattern(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1'),
Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1', Pattern_type.INCLUDE),
Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2'),
Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2', Pattern_type.INCLUDE),
]
assert config == {
'btrfs': {},
}

View File

@@ -0,0 +1,81 @@
from flexmock import flexmock
from borgmatic.borg.pattern import Pattern, Pattern_type
from borgmatic.hooks.data_source import lvm as module
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
config = {'lvm': {}}
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
logical_volumes = (
module.Logical_volume(
name='lvolume1',
device_path='/dev/lvolume1',
mount_point='/mnt/lvolume1',
contained_patterns=(Pattern('/mnt/lvolume1/subdir'),),
),
module.Logical_volume(
name='lvolume2',
device_path='/dev/lvolume2',
mount_point='/mnt/lvolume2',
contained_patterns=(Pattern('/mnt/lvolume2'),),
),
)
flexmock(module).should_receive('get_logical_volumes').and_return(logical_volumes)
flexmock(module.os).should_receive('getpid').and_return(1234)
flexmock(module).should_receive('snapshot_logical_volume').with_args(
'lvcreate',
'lvolume1_borgmatic-1234',
'/dev/lvolume1',
module.DEFAULT_SNAPSHOT_SIZE,
).once()
flexmock(module).should_receive('snapshot_logical_volume').with_args(
'lvcreate',
'lvolume2_borgmatic-1234',
'/dev/lvolume2',
module.DEFAULT_SNAPSHOT_SIZE,
).once()
flexmock(module).should_receive('get_snapshots').with_args(
'lvs',
snapshot_name='lvolume1_borgmatic-1234',
).and_return(
(module.Snapshot(name='lvolume1_borgmatic-1234', device_path='/dev/lvolume1_snap'),),
)
flexmock(module).should_receive('get_snapshots').with_args(
'lvs',
snapshot_name='lvolume2_borgmatic-1234',
).and_return(
(module.Snapshot(name='lvolume2_borgmatic-1234', device_path='/dev/lvolume2_snap'),),
)
flexmock(module.hashlib).should_receive('shake_256').and_return(
flexmock(hexdigest=lambda length: 'b33f'),
)
flexmock(module).should_receive('mount_snapshot').with_args(
'mount',
'/dev/lvolume1_snap',
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume1',
).once()
flexmock(module).should_receive('mount_snapshot').with_args(
'mount',
'/dev/lvolume2_snap',
'/run/borgmatic/lvm_snapshots/b33f/mnt/lvolume2',
).once()
assert (
module.dump_data_sources(
hook_config=config['lvm'],
config=config,
config_paths=('test.yaml',),
borgmatic_runtime_directory='/run/borgmatic',
patterns=patterns,
dry_run=False,
)
== []
)
assert patterns == [
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir', Pattern_type.INCLUDE),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2', Pattern_type.INCLUDE),
]

View File

@@ -0,0 +1,48 @@
import os
from flexmock import flexmock
from borgmatic.borg.pattern import Pattern, Pattern_type
from borgmatic.hooks.data_source import zfs as module
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
dataset = flexmock(
name='dataset',
mount_point='/mnt/dataset',
contained_patterns=(Pattern('/mnt/dataset/subdir'),),
)
flexmock(module).should_receive('get_datasets_to_backup').and_return((dataset,))
flexmock(module.os).should_receive('getpid').and_return(1234)
full_snapshot_name = 'dataset@borgmatic-1234'
flexmock(module).should_receive('snapshot_dataset').with_args(
'zfs',
full_snapshot_name,
).once()
flexmock(module.hashlib).should_receive('shake_256').and_return(
flexmock(hexdigest=lambda length: 'b33f'),
)
snapshot_mount_path = '/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset'
flexmock(module).should_receive('mount_snapshot').with_args(
'mount',
full_snapshot_name,
module.os.path.normpath(snapshot_mount_path),
).once()
patterns = [Pattern('/mnt/dataset/subdir')]
assert (
module.dump_data_sources(
hook_config={},
config={'source_directories': '/mnt/dataset', 'zfs': {}},
config_paths=('test.yaml',),
borgmatic_runtime_directory='/run/borgmatic',
patterns=patterns,
dry_run=False,
)
== []
)
assert patterns == [
Pattern(os.path.join(snapshot_mount_path, 'subdir')),
Pattern(os.path.join(snapshot_mount_path, 'subdir'), Pattern_type.INCLUDE),
]

View File

@@ -22,6 +22,18 @@ def test_dump_data_sources_creates_manifest_file():
{'borgmatic_version': '1.0.0', 'config_paths': ('test.yaml',)},
manifest_file,
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/bootstrap', source=module.borgmatic.borg.pattern.Pattern_source.HOOK
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'test.yaml', source=module.borgmatic.borg.pattern.Pattern_source.HOOK
),
).once()
module.dump_data_sources(
hook_config=None,
@@ -36,6 +48,7 @@ def test_dump_data_sources_creates_manifest_file():
def test_dump_data_sources_with_store_config_files_false_does_not_create_manifest_file():
flexmock(module.os).should_receive('makedirs').never()
flexmock(module.json).should_receive('dump').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
hook_config = {'store_config_files': False}
module.dump_data_sources(
@@ -51,7 +64,7 @@ def test_dump_data_sources_with_store_config_files_false_does_not_create_manifes
def test_dump_data_sources_with_dry_run_does_not_create_manifest_file():
flexmock(module.os).should_receive('makedirs').never()
flexmock(module.json).should_receive('dump').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
module.dump_data_sources(
hook_config=None,
config={},

View File

@@ -399,8 +399,8 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_
assert module.make_borg_snapshot_pattern(subvolume_path, pattern) == expected_pattern
def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
def test_dump_data_sources_snapshots_each_subvolume_and_replaces_patterns():
patterns = [Pattern('/foo'), Pattern('/mnt/subvol1'), Pattern('/mnt/subvol2')]
config = {'btrfs': {}}
flexmock(module).should_receive('get_subvolumes').and_return(
(
@@ -409,26 +409,26 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
)
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol1',
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol2',
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).once()
flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
'/mnt/subvol1',
).and_return(
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
@@ -437,7 +437,7 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
'/mnt/subvol2',
).and_return(
Pattern(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
@@ -445,11 +445,43 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol1',
object,
).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol2',
object,
).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
).and_return(Pattern('/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/subvol1'),
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/subvol2'),
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
).once()
assert (
module.dump_data_sources(
@@ -463,21 +495,6 @@ def test_dump_data_sources_snapshots_each_subvolume_and_updates_patterns():
== []
)
assert patterns == [
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
Pattern(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
]
assert config == {
'btrfs': {},
}
@@ -490,18 +507,18 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
(module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
)
flexmock(module).should_receive('snapshot_subvolume').with_args(
'/usr/local/bin/btrfs',
'/mnt/subvol1',
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
'/mnt/subvol1',
).and_return(
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
@@ -509,7 +526,23 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol1',
object,
).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/subvol1'),
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
).once()
assert (
module.dump_data_sources(
@@ -523,15 +556,6 @@ def test_dump_data_sources_uses_custom_btrfs_command_in_commands():
== []
)
assert patterns == [
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
]
assert config == {
'btrfs': {
'btrfs_command': '/usr/local/bin/btrfs',
@@ -550,18 +574,18 @@ def test_dump_data_sources_with_findmnt_command_warns():
(module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
).once()
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
)
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol1',
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
'/mnt/subvol1',
).and_return(
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
@@ -569,7 +593,23 @@ def test_dump_data_sources_with_findmnt_command_warns():
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol1',
object,
).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
).and_return(Pattern('/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/subvol1'),
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1/.borgmatic-snapshot-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
).once()
assert (
module.dump_data_sources(
@@ -583,15 +623,6 @@ def test_dump_data_sources_with_findmnt_command_warns():
== []
)
assert patterns == [
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
]
assert config == {
'btrfs': {
'findmnt_command': '/usr/local/bin/findmnt',
@@ -606,10 +637,12 @@ def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
(module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
)
flexmock(module).should_receive('snapshot_subvolume').never()
flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(
@@ -623,7 +656,6 @@ def test_dump_data_sources_with_dry_run_skips_snapshot_and_patterns_update():
== []
)
assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
assert config == {'btrfs': {}}
@@ -634,6 +666,8 @@ def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patter
flexmock(module).should_receive('make_snapshot_path').never()
flexmock(module).should_receive('snapshot_subvolume').never()
flexmock(module).should_receive('make_snapshot_exclude_pattern').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(
@@ -647,95 +681,9 @@ def test_dump_data_sources_without_matching_subvolumes_skips_snapshot_and_patter
== []
)
assert patterns == [Pattern('/foo'), Pattern('/mnt/subvol1')]
assert config == {'btrfs': {}}
def test_dump_data_sources_snapshots_adds_to_existing_exclude_patterns():
patterns = [Pattern('/foo'), Pattern('/mnt/subvol1')]
config = {'btrfs': {}, 'exclude_patterns': ['/bar']}
flexmock(module).should_receive('get_subvolumes').and_return(
(
module.Subvolume('/mnt/subvol1', contained_patterns=(Pattern('/mnt/subvol1'),)),
module.Subvolume('/mnt/subvol2', contained_patterns=(Pattern('/mnt/subvol2'),)),
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
)
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol1',
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('snapshot_subvolume').with_args(
'btrfs',
'/mnt/subvol2',
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
).once()
flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
'/mnt/subvol1',
).and_return(
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
)
flexmock(module).should_receive('make_snapshot_exclude_pattern').with_args(
'/mnt/subvol2',
).and_return(
Pattern(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
)
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol1',
object,
).and_return(Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'))
flexmock(module).should_receive('make_borg_snapshot_pattern').with_args(
'/mnt/subvol2',
object,
).and_return(Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'))
assert (
module.dump_data_sources(
hook_config=config['btrfs'],
config=config,
config_paths=('test.yaml',),
borgmatic_runtime_directory='/run/borgmatic',
patterns=patterns,
dry_run=False,
)
== []
)
assert patterns == [
Pattern('/foo'),
Pattern('/mnt/subvol1/.borgmatic-1234/mnt/subvol1'),
Pattern(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
Pattern('/mnt/subvol2/.borgmatic-1234/mnt/subvol2'),
Pattern(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2/.borgmatic-1234',
Pattern_type.NO_RECURSE,
Pattern_style.FNMATCH,
),
]
assert config == {
'btrfs': {},
'exclude_patterns': ['/bar'],
}
def test_remove_data_source_dumps_deletes_snapshots():
config = {'btrfs': {}}
flexmock(module).should_receive('get_subvolumes').and_return(
@@ -745,84 +693,90 @@ def test_remove_data_source_dumps_deletes_snapshots():
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol1/.borgmatic-*/mnt/subvol1',
).and_return(
('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
),
)
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol2/.borgmatic-*/mnt/subvol2',
).and_return(
('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
),
)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
).and_return(False)
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs',
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).once()
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs',
'/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
).once()
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs',
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).once()
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs',
'/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
).never()
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-1234',
'/mnt/subvol1/.borgmatic-snapshot-1234',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-5678',
'/mnt/subvol1/.borgmatic-snapshot-5678',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-1234',
'/mnt/subvol2/.borgmatic-snapshot-1234',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-5678',
'/mnt/subvol2/.borgmatic-snapshot-5678',
).and_return(True)
flexmock(module.shutil).should_receive('rmtree').with_args(
'/mnt/subvol1/.borgmatic-1234',
'/mnt/subvol1/.borgmatic-snapshot-1234',
).once()
flexmock(module.shutil).should_receive('rmtree').with_args(
'/mnt/subvol1/.borgmatic-5678',
'/mnt/subvol1/.borgmatic-snapshot-5678',
).once()
flexmock(module.shutil).should_receive('rmtree').with_args(
'/mnt/subvol2/.borgmatic-1234',
'/mnt/subvol2/.borgmatic-snapshot-1234',
).once()
flexmock(module.shutil).should_receive('rmtree').with_args(
'/mnt/subvol2/.borgmatic-5678',
'/mnt/subvol2/.borgmatic-snapshot-5678',
).never()
module.remove_data_source_dumps(
@@ -901,44 +855,50 @@ def test_remove_data_source_dumps_with_dry_run_skips_deletes():
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol1/.borgmatic-*/mnt/subvol1',
).and_return(
('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
),
)
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol2/.borgmatic-*/mnt/subvol2',
).and_return(
('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
),
)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
).and_return(False)
flexmock(module).should_receive('delete_snapshot').never()
flexmock(module.shutil).should_receive('rmtree').never()
@@ -980,21 +940,21 @@ def test_remove_data_source_without_snapshots_skips_deletes():
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
flexmock(module.glob).should_receive('glob').and_return(())
@@ -1020,44 +980,50 @@ def test_remove_data_source_dumps_with_delete_snapshot_file_not_found_error_bail
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol1/.borgmatic-*/mnt/subvol1',
).and_return(
('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
),
)
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol2/.borgmatic-*/mnt/subvol2',
).and_return(
('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
),
)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
).and_return(False)
flexmock(module).should_receive('delete_snapshot').and_raise(FileNotFoundError)
flexmock(module.shutil).should_receive('rmtree').never()
@@ -1080,44 +1046,50 @@ def test_remove_data_source_dumps_with_delete_snapshot_called_process_error_bail
),
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol1').and_return(
'/mnt/subvol1/.borgmatic-1234/./mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/./mnt/subvol1',
)
flexmock(module).should_receive('make_snapshot_path').with_args('/mnt/subvol2').and_return(
'/mnt/subvol2/.borgmatic-1234/./mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/./mnt/subvol2',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol1/.borgmatic-*/mnt/subvol1')
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/mnt/subvol2/.borgmatic-*/mnt/subvol2')
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol1/.borgmatic-*/mnt/subvol1',
).and_return(
('/mnt/subvol1/.borgmatic-1234/mnt/subvol1', '/mnt/subvol1/.borgmatic-5678/mnt/subvol1'),
(
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
),
)
flexmock(module.glob).should_receive('glob').with_args(
'/mnt/subvol2/.borgmatic-*/mnt/subvol2',
).and_return(
('/mnt/subvol2/.borgmatic-1234/mnt/subvol2', '/mnt/subvol2/.borgmatic-5678/mnt/subvol2'),
(
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
),
)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-1234/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-1234/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol1/.borgmatic-5678/mnt/subvol1',
'/mnt/subvol1/.borgmatic-snapshot-5678/mnt/subvol1',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-1234/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-1234/mnt/subvol2',
).and_return(True)
flexmock(module.os.path).should_receive('isdir').with_args(
'/mnt/subvol2/.borgmatic-5678/mnt/subvol2',
'/mnt/subvol2/.borgmatic-snapshot-5678/mnt/subvol2',
).and_return(False)
flexmock(module).should_receive('delete_snapshot').and_raise(
module.subprocess.CalledProcessError(1, 'command', 'error'),
@@ -1140,29 +1112,37 @@ def test_remove_data_source_dumps_with_root_subvolume_skips_duplicate_removal():
)
flexmock(module).should_receive('make_snapshot_path').with_args('/').and_return(
'/.borgmatic-1234',
'/.borgmatic-snapshot-1234',
)
flexmock(module.borgmatic.config.paths).should_receive(
'replace_temporary_subdirectory_with_glob',
).with_args(
'/.borgmatic-1234',
'/.borgmatic-snapshot-1234',
temporary_directory_prefix=module.BORGMATIC_SNAPSHOT_PREFIX,
).and_return('/.borgmatic-*')
flexmock(module.glob).should_receive('glob').with_args('/.borgmatic-*').and_return(
('/.borgmatic-1234', '/.borgmatic-5678'),
('/.borgmatic-snapshot-1234', '/.borgmatic-snapshot-5678'),
)
flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-1234').and_return(
flexmock(module.os.path).should_receive('isdir').with_args(
'/.borgmatic-snapshot-1234'
).and_return(
True,
).and_return(False)
flexmock(module.os.path).should_receive('isdir').with_args('/.borgmatic-5678').and_return(
flexmock(module.os.path).should_receive('isdir').with_args(
'/.borgmatic-snapshot-5678'
).and_return(
True,
).and_return(False)
flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-1234').once()
flexmock(module).should_receive('delete_snapshot').with_args('btrfs', '/.borgmatic-5678').once()
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs', '/.borgmatic-snapshot-1234'
).once()
flexmock(module).should_receive('delete_snapshot').with_args(
'btrfs', '/.borgmatic-snapshot-5678'
).once()
flexmock(module.os.path).should_receive('isdir').with_args('').and_return(False)

View File

@@ -155,3 +155,116 @@ def test_get_ip_from_container_with_broken_output_errors():
module.get_ip_from_container('yolo')
assert 'Could not decode JSON output' in str(exc_info.value)
def test_inject_pattern_prepends_pattern_in_list():
patterns = [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
]
module.inject_pattern(
patterns,
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE,
),
)
assert patterns == [
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE,
),
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
]
def test_inject_pattern_with_root_pattern_prepends_it_along_with_corresponding_include_pattern():
patterns = [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
]
module.inject_pattern(
patterns,
module.borgmatic.borg.pattern.Pattern('/foo/bar'),
)
assert patterns == [
module.borgmatic.borg.pattern.Pattern('/foo/bar'),
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.INCLUDE,
),
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
]
def test_replace_pattern_swaps_out_pattern_in_place():
patterns = [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
module.borgmatic.borg.pattern.Pattern('/lib'),
]
module.replace_pattern(
patterns,
module.borgmatic.borg.pattern.Pattern('/var'),
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE,
),
)
assert patterns == [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.EXCLUDE,
),
module.borgmatic.borg.pattern.Pattern('/lib'),
]
def test_replace_pattern_with_unknown_pattern_falls_back_to_injecting():
patterns = [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
module.borgmatic.borg.pattern.Pattern('/lib'),
]
flexmock(module).should_receive('inject_pattern').with_args(
patterns, module.borgmatic.borg.pattern.Pattern('/foo/bar')
).once()
module.replace_pattern(
patterns,
module.borgmatic.borg.pattern.Pattern('/unknown'),
module.borgmatic.borg.pattern.Pattern('/foo/bar'),
)
def test_replace_pattern_with_root_pattern_swaps_it_in_along_with_corresponding_include_pattern():
patterns = [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/var'),
module.borgmatic.borg.pattern.Pattern('/lib'),
]
module.replace_pattern(
patterns,
module.borgmatic.borg.pattern.Pattern('/var'),
module.borgmatic.borg.pattern.Pattern('/foo/bar'),
)
assert patterns == [
module.borgmatic.borg.pattern.Pattern('/etc'),
module.borgmatic.borg.pattern.Pattern('/foo/bar'),
module.borgmatic.borg.pattern.Pattern(
'/foo/bar',
type=module.borgmatic.borg.pattern.Pattern_type.INCLUDE,
),
module.borgmatic.borg.pattern.Pattern('/lib'),
]

View File

@@ -294,7 +294,7 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_
)
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
def test_dump_data_sources_snapshots_and_mounts_and_replaces_patterns():
config = {'lvm': {}}
patterns = [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
logical_volumes = (
@@ -360,6 +360,22 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
logical_volumes[1],
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume1/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume2'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -373,11 +389,6 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
== []
)
assert patterns == [
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
]
def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
config = {'lvm': {}}
@@ -385,6 +396,7 @@ def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
flexmock(module).should_receive('get_logical_volumes').and_return(())
flexmock(module).should_receive('snapshot_logical_volume').never()
flexmock(module).should_receive('mount_snapshot').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
assert (
module.dump_data_sources(
@@ -398,8 +410,6 @@ def test_dump_data_sources_with_no_logical_volumes_skips_snapshots():
== []
)
assert patterns == [Pattern('/mnt/lvolume1/subdir'), Pattern('/mnt/lvolume2')]
def test_dump_data_sources_uses_snapshot_size_for_snapshot():
config = {'lvm': {'snapshot_size': '1000PB'}}
@@ -467,6 +477,22 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot():
logical_volumes[1],
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume1/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume2'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -480,11 +506,6 @@ def test_dump_data_sources_uses_snapshot_size_for_snapshot():
== []
)
assert patterns == [
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
]
def test_dump_data_sources_uses_custom_commands():
config = {
@@ -559,6 +580,22 @@ def test_dump_data_sources_uses_custom_commands():
logical_volumes[1],
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume1/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume2'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -572,11 +609,6 @@ def test_dump_data_sources_uses_custom_commands():
== []
)
assert patterns == [
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
]
def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patterns():
config = {'lvm': {}}
@@ -601,6 +633,7 @@ def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patte
flexmock(module).should_receive('snapshot_logical_volume').never()
flexmock(module).should_receive('get_snapshots').never()
flexmock(module).should_receive('mount_snapshot').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
assert (
module.dump_data_sources(
@@ -614,11 +647,6 @@ def test_dump_data_sources_with_dry_run_skips_snapshots_and_does_not_touch_patte
== []
)
assert patterns == [
Pattern('/mnt/lvolume1/subdir'),
Pattern('/mnt/lvolume2'),
]
def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
config = {'lvm': {}}
@@ -686,6 +714,22 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
logical_volumes[1],
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume1/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/lvolume2'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -699,12 +743,6 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
== []
)
assert patterns == [
Pattern('/hmm'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume1/subdir'),
Pattern('/run/borgmatic/lvm_snapshots/b33f/./mnt/lvolume2'),
]
def test_dump_data_sources_with_missing_snapshot_errors():
config = {'lvm': {}}
@@ -747,6 +785,7 @@ def test_dump_data_sources_with_missing_snapshot_errors():
snapshot_name='lvolume2_borgmatic-1234',
).never()
flexmock(module).should_receive('mount_snapshot').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
with pytest.raises(ValueError):
module.dump_data_sources(

View File

@@ -409,6 +409,13 @@ def test_dump_data_sources_dumps_each_database():
module.borgmatic.actions.restore.Dump('mariadb_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mariadb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -458,6 +465,13 @@ def test_dump_data_sources_dumps_with_password():
module.borgmatic.actions.restore.Dump('mariadb_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mariadb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
[database],
@@ -509,6 +523,13 @@ def test_dump_data_sources_dumps_with_environment_password_transport_passes_pass
module.borgmatic.actions.restore.Dump('mariadb_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mariadb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
[database],
@@ -547,6 +568,13 @@ def test_dump_data_sources_dumps_all_databases_at_once():
module.borgmatic.actions.restore.Dump('mariadb_databases', 'all'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mariadb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -589,6 +617,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
module.borgmatic.actions.restore.Dump('mariadb_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mariadb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -614,6 +649,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
'databases/localhost/all',
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
with pytest.raises(ValueError):
assert module.dump_data_sources(
@@ -637,6 +673,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
'databases/localhost/all',
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(

View File

@@ -47,6 +47,13 @@ def test_dump_data_sources_runs_mongodump_for_each_database():
module.borgmatic.actions.restore.Dump('mongodb_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -70,6 +77,7 @@ def test_dump_data_sources_with_dry_run_skips_mongodump():
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(
@@ -118,6 +126,13 @@ def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -176,6 +191,13 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -207,6 +229,13 @@ def test_dump_data_sources_runs_mongodump_with_directory_format():
module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -250,6 +279,13 @@ def test_dump_data_sources_runs_mongodump_with_options():
module.borgmatic.actions.restore.Dump('mongodb_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -282,6 +318,13 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
module.borgmatic.actions.restore.Dump('mongodb_databases', 'all'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mongodb_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,

View File

@@ -296,6 +296,13 @@ def test_dump_data_sources_dumps_each_database():
module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mysql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -345,6 +352,13 @@ def test_dump_data_sources_dumps_with_password():
module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mysql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
[database],
@@ -396,6 +410,13 @@ def test_dump_data_sources_dumps_with_environment_password_transport_passes_pass
module.borgmatic.actions.restore.Dump('mysql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mysql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
[database],
@@ -435,6 +456,13 @@ def test_dump_data_sources_dumps_all_databases_at_once():
module.borgmatic.actions.restore.Dump('mysql_databases', 'all'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mysql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -477,6 +505,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
module.borgmatic.actions.restore.Dump('mysql_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/mysql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -502,6 +537,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
'databases/localhost/all',
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
with pytest.raises(ValueError):
assert module.dump_data_sources(
@@ -525,6 +561,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
'databases/localhost/all',
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(

View File

@@ -281,6 +281,13 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -300,6 +307,7 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump():
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
flexmock(module).should_receive('make_dump_path').and_return('')
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
with pytest.raises(ValueError):
module.dump_data_sources(
@@ -317,6 +325,7 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
flexmock(module).should_receive('make_dump_path').and_return('')
flexmock(module).should_receive('database_names_to_dump').and_return(())
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(
@@ -352,6 +361,13 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'bar'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -383,6 +399,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_dump():
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(
@@ -441,6 +458,13 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -494,6 +518,13 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -547,6 +578,13 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -595,6 +633,13 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -649,6 +694,13 @@ def test_dump_data_sources_runs_pg_dump_with_string_compression():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -703,6 +755,13 @@ def test_dump_data_sources_runs_pg_dump_with_integer_compression():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -756,6 +815,13 @@ def test_dump_data_sources_runs_pg_dump_with_options():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -795,6 +861,13 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'all'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,
@@ -846,6 +919,13 @@ def test_dump_data_sources_runs_non_default_pg_dump():
module.borgmatic.actions.restore.Dump('postgresql_databases', 'foo'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/postgresql_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert module.dump_data_sources(
databases,

View File

@@ -33,6 +33,13 @@ def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
module.borgmatic.actions.restore.Dump('sqlite_databases', 'database'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -71,6 +78,13 @@ def test_dump_data_sources_dumps_each_database():
module.borgmatic.actions.restore.Dump('sqlite_databases', 'database2'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -115,6 +129,13 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -164,6 +185,13 @@ def test_dump_data_sources_runs_non_default_sqlite_with_path_injection_attack_ge
module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -199,6 +227,13 @@ def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
module.borgmatic.actions.restore.Dump('sqlite_databases', 'database1'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -236,6 +271,13 @@ def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
module.borgmatic.actions.restore.Dump('sqlite_databases', 'all'),
],
).once()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').with_args(
object,
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/sqlite_databases',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -261,6 +303,7 @@ def test_dump_data_sources_does_not_dump_if_dry_run():
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
flexmock(module.dump).should_receive('write_data_source_dumps_metadata').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('inject_pattern').never()
assert (
module.dump_data_sources(

View File

@@ -1,5 +1,3 @@
import os
import pytest
from flexmock import flexmock
@@ -302,7 +300,7 @@ def test_make_borg_snapshot_pattern_includes_slashdot_hack_and_stripped_pattern_
)
def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
def test_dump_data_sources_snapshots_and_mounts_and_replaces_patterns():
dataset = flexmock(
name='dataset',
mount_point='/mnt/dataset',
@@ -330,6 +328,14 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
patterns = [Pattern('/mnt/dataset/subdir')]
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/dataset/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
assert (
module.dump_data_sources(
@@ -343,14 +349,13 @@ def test_dump_data_sources_snapshots_and_mounts_and_updates_patterns():
== []
)
assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
def test_dump_data_sources_with_no_datasets_skips_snapshots():
flexmock(module).should_receive('get_datasets_to_backup').and_return(())
flexmock(module.os).should_receive('getpid').and_return(1234)
flexmock(module).should_receive('snapshot_dataset').never()
flexmock(module).should_receive('mount_snapshot').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
patterns = [Pattern('/mnt/dataset')]
assert (
@@ -395,6 +400,14 @@ def test_dump_data_sources_uses_custom_commands():
dataset,
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/dataset/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
patterns = [Pattern('/mnt/dataset/subdir')]
hook_config = {
'zfs_command': '/usr/local/bin/zfs',
@@ -416,8 +429,6 @@ def test_dump_data_sources_uses_custom_commands():
== []
)
assert patterns == [Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patterns():
flexmock(module).should_receive('get_datasets_to_backup').and_return(
@@ -426,6 +437,7 @@ def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patter
flexmock(module.os).should_receive('getpid').and_return(1234)
flexmock(module).should_receive('snapshot_dataset').never()
flexmock(module).should_receive('mount_snapshot').never()
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').never()
patterns = [Pattern('/mnt/dataset')]
assert (
@@ -440,8 +452,6 @@ def test_dump_data_sources_with_dry_run_skips_commands_and_does_not_touch_patter
== []
)
assert patterns == [Pattern('/mnt/dataset')]
def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained_patterns():
dataset = flexmock(
@@ -470,6 +480,14 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
dataset,
'/run/borgmatic',
).and_return(Pattern('/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir'))
flexmock(module.borgmatic.hooks.data_source.config).should_receive('replace_pattern').with_args(
object,
Pattern('/mnt/dataset/subdir'),
module.borgmatic.borg.pattern.Pattern(
'/run/borgmatic/zfs_snapshots/b33f/./mnt/dataset/subdir',
source=module.borgmatic.borg.pattern.Pattern_source.HOOK,
),
).once()
patterns = [Pattern('/hmm')]
assert (
@@ -484,8 +502,6 @@ def test_dump_data_sources_ignores_mismatch_between_given_patterns_and_contained
== []
)
assert patterns == [Pattern('/hmm'), Pattern(os.path.join(snapshot_mount_path, 'subdir'))]
def test_get_all_snapshots_parses_list_output():
flexmock(module.borgmatic.execute).should_receive(

View File

@@ -33,12 +33,18 @@ pass_env = COVERAGE_FILE
commands =
pytest {posargs} --no-cov tests/end-to-end
[testenv:lint-fix]
[testenv:lint]
deps = []
skip_install = true
commands =
ruff check --diff {posargs}
[testenv:lint-fix]
deps = []
skip_install = true
commands =
ruff check --fix {posargs}
[testenv:format]
deps = []
skip_install = true