diff --git a/NEWS b/NEWS
index 364e82e6..74aa862e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,9 @@
1.9.2.dev0
* #932: Fix missing build backend setting in pyproject.toml to allow Fedora builds.
+ * #934: Update the logic that probes for the borgmatic streaming database dump, bootstrap
+ metadata, and check state directories to support more platforms and use cases.
+ * #934: Add the "RuntimeDirectory" and "StateDirectory" options to the sample systemd service
+ file to support the new runtime and state directory logic.
1.9.1
* #928: Fix the user runtime directory location on macOS (and possibly Cygwin).
diff --git a/borgmatic/actions/check.py b/borgmatic/actions/check.py
index d5f7fbe2..928ac481 100644
--- a/borgmatic/actions/check.py
+++ b/borgmatic/actions/check.py
@@ -403,16 +403,22 @@ BORG_DIRECTORY_FILE_TYPE = 'd'
def collect_spot_check_archive_paths(
- repository, archive, config, local_borg_version, global_arguments, local_path, remote_path
+ repository,
+ archive,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ borgmatic_runtime_directory,
):
'''
Given a repository configuration dict, the name of the latest archive, a configuration dict, the
- local Borg version, global arguments as an argparse.Namespace instance, the local Borg path, and
- the remote Borg path, collect the paths from the given archive (but only include files and
- symlinks and exclude borgmatic runtime directories).
+ local Borg version, global arguments as an argparse.Namespace instance, the local Borg path, the
+ remote Borg path, and the borgmatic runtime directory, collect the paths from the given archive
+ (but only include files and symlinks and exclude borgmatic runtime directories).
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
- borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
return tuple(
path
@@ -445,7 +451,7 @@ def compare_spot_check_hashes(
global_arguments,
local_path,
remote_path,
- log_label,
+ log_prefix,
source_paths,
):
'''
@@ -469,7 +475,7 @@ def compare_spot_check_hashes(
if os.path.exists(os.path.join(working_directory or '', source_path))
}
logger.debug(
- f'{log_label}: Sampling {sample_count} source paths (~{spot_check_config["data_sample_percentage"]}%) for spot check'
+ f'{log_prefix}: Sampling {sample_count} source paths (~{spot_check_config["data_sample_percentage"]}%) for spot check'
)
source_sample_paths_iterator = iter(source_sample_paths)
@@ -546,18 +552,19 @@ def spot_check(
global_arguments,
local_path,
remote_path,
+ borgmatic_runtime_directory,
):
'''
Given a repository dict, a loaded configuration dict, the local Borg version, global arguments
- as an argparse.Namespace instance, the local Borg path, and the remote Borg path, perform a spot
- check for the latest archive in the given repository.
+ as an argparse.Namespace instance, the local Borg path, the remote Borg path, and the borgmatic
+ runtime directory, perform a spot check for the latest archive in the given repository.
A spot check compares file counts and also the hashes for a random sampling of source files on
disk to those stored in the latest archive. If any differences are beyond configured tolerances,
then the check fails.
'''
- log_label = f'{repository.get("label", repository["path"])}'
- logger.debug(f'{log_label}: Running spot check')
+ log_prefix = f'{repository.get("label", repository["path"])}'
+ logger.debug(f'{log_prefix}: Running spot check')
try:
spot_check_config = next(
@@ -579,7 +586,7 @@ def spot_check(
local_path,
remote_path,
)
- logger.debug(f'{log_label}: {len(source_paths)} total source paths for spot check')
+ logger.debug(f'{log_prefix}: {len(source_paths)} total source paths for spot check')
archive = borgmatic.borg.repo_list.resolve_archive_name(
repository['path'],
@@ -590,7 +597,7 @@ def spot_check(
local_path,
remote_path,
)
- logger.debug(f'{log_label}: Using archive {archive} for spot check')
+ logger.debug(f'{log_prefix}: Using archive {archive} for spot check')
archive_paths = collect_spot_check_archive_paths(
repository,
@@ -600,8 +607,9 @@ def spot_check(
global_arguments,
local_path,
remote_path,
+ borgmatic_runtime_directory,
)
- logger.debug(f'{log_label}: {len(archive_paths)} total archive paths for spot check')
+ logger.debug(f'{log_prefix}: {len(archive_paths)} total archive paths for spot check')
# Calculate the percentage delta between the source paths count and the archive paths count, and
# compare that delta to the configured count tolerance percentage.
@@ -609,10 +617,10 @@ def spot_check(
if count_delta_percentage > spot_check_config['count_tolerance_percentage']:
logger.debug(
- f'{log_label}: Paths in source paths but not latest archive: {", ".join(set(source_paths) - set(archive_paths)) or "none"}'
+ f'{log_prefix}: Paths in source paths but not latest archive: {", ".join(set(source_paths) - set(archive_paths)) or "none"}'
)
logger.debug(
- f'{log_label}: Paths in latest archive but not source paths: {", ".join(set(archive_paths) - set(source_paths)) or "none"}'
+ f'{log_prefix}: Paths in latest archive but not source paths: {", ".join(set(archive_paths) - set(source_paths)) or "none"}'
)
raise ValueError(
f'Spot check failed: {count_delta_percentage:.2f}% file count delta between source paths and latest archive (tolerance is {spot_check_config["count_tolerance_percentage"]}%)'
@@ -626,25 +634,25 @@ def spot_check(
global_arguments,
local_path,
remote_path,
- log_label,
+ log_prefix,
source_paths,
)
# Error if the percentage of failing hashes exceeds the configured tolerance percentage.
- logger.debug(f'{log_label}: {len(failing_paths)} non-matching spot check hashes')
+ logger.debug(f'{log_prefix}: {len(failing_paths)} non-matching spot check hashes')
data_tolerance_percentage = spot_check_config['data_tolerance_percentage']
failing_percentage = (len(failing_paths) / len(source_paths)) * 100
if failing_percentage > data_tolerance_percentage:
logger.debug(
- f'{log_label}: Source paths with data not matching the latest archive: {", ".join(failing_paths)}'
+ f'{log_prefix}: Source paths with data not matching the latest archive: {", ".join(failing_paths)}'
)
raise ValueError(
f'Spot check failed: {failing_percentage:.2f}% of source paths with data not matching the latest archive (tolerance is {data_tolerance_percentage}%)'
)
logger.info(
- f'{log_label}: Spot check passed with a {count_delta_percentage:.2f}% file count delta and a {failing_percentage:.2f}% file data delta'
+ f'{log_prefix}: Spot check passed with a {count_delta_percentage:.2f}% file count delta and a {failing_percentage:.2f}% file data delta'
)
@@ -678,7 +686,9 @@ def run_check(
**hook_context,
)
- logger.info(f'{repository.get("label", repository["path"])}: Running consistency checks')
+ log_prefix = repository.get('label', repository['path'])
+ logger.info(f'{log_prefix}: Running consistency checks')
+
repository_id = borgmatic.borg.check.get_repository_id(
repository['path'],
config,
@@ -730,14 +740,18 @@ def run_check(
write_check_time(make_check_time_path(config, repository_id, 'extract'))
if 'spot' in checks:
- spot_check(
- repository,
- config,
- local_borg_version,
- global_arguments,
- local_path,
- remote_path,
- )
+ with borgmatic.config.paths.Runtime_directory(
+ config, log_prefix
+ ) as borgmatic_runtime_directory:
+ spot_check(
+ repository,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ borgmatic_runtime_directory,
+ )
write_check_time(make_check_time_path(config, repository_id, 'spot'))
borgmatic.hooks.command.execute_hook(
diff --git a/borgmatic/actions/config/bootstrap.py b/borgmatic/actions/config/bootstrap.py
index bd1c6bde..17cf60f5 100644
--- a/borgmatic/actions/config/bootstrap.py
+++ b/borgmatic/actions/config/bootstrap.py
@@ -38,37 +38,44 @@ def get_config_paths(archive_name, bootstrap_arguments, global_arguments, local_
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(
{'borgmatic_source_directory': bootstrap_arguments.borgmatic_source_directory}
)
- borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(
- {'user_runtime_directory': bootstrap_arguments.user_runtime_directory}
- )
config = make_bootstrap_config(bootstrap_arguments)
# Probe for the manifest file in multiple locations, as the default location has moved to the
# borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
# still want to support reading the manifest from previously created archives as well.
- for base_directory in ('borgmatic', borgmatic_runtime_directory, borgmatic_source_directory):
- borgmatic_manifest_path = os.path.join(base_directory, 'bootstrap', 'manifest.json')
+ with borgmatic.config.paths.Runtime_directory(
+ {'user_runtime_directory': bootstrap_arguments.user_runtime_directory},
+ bootstrap_arguments.repository,
+ ) as borgmatic_runtime_directory:
+ for base_directory in (
+ 'borgmatic',
+ borgmatic.config.paths.make_runtime_directory_glob(borgmatic_runtime_directory),
+ borgmatic_source_directory,
+ ):
+ borgmatic_manifest_path = 'sh:' + os.path.join(
+ base_directory, 'bootstrap', 'manifest.json'
+ )
- extract_process = borgmatic.borg.extract.extract_archive(
- global_arguments.dry_run,
- bootstrap_arguments.repository,
- archive_name,
- [borgmatic_manifest_path],
- config,
- local_borg_version,
- global_arguments,
- local_path=bootstrap_arguments.local_path,
- remote_path=bootstrap_arguments.remote_path,
- extract_to_stdout=True,
- )
- manifest_json = extract_process.stdout.read()
+ extract_process = borgmatic.borg.extract.extract_archive(
+ global_arguments.dry_run,
+ bootstrap_arguments.repository,
+ archive_name,
+ [borgmatic_manifest_path],
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path=bootstrap_arguments.local_path,
+ remote_path=bootstrap_arguments.remote_path,
+ extract_to_stdout=True,
+ )
+ manifest_json = extract_process.stdout.read()
- if manifest_json:
- break
- else:
- raise ValueError(
- 'Cannot read configuration paths from archive due to missing bootstrap manifest'
- )
+ if manifest_json:
+ break
+ else:
+ raise ValueError(
+ 'Cannot read configuration paths from archive due to missing bootstrap manifest'
+ )
try:
manifest_data = json.loads(manifest_json)
diff --git a/borgmatic/actions/create.py b/borgmatic/actions/create.py
index aa8d6bc6..03a5ca52 100644
--- a/borgmatic/actions/create.py
+++ b/borgmatic/actions/create.py
@@ -14,15 +14,15 @@ import borgmatic.hooks.dump
logger = logging.getLogger(__name__)
-def create_borgmatic_manifest(config, config_paths, dry_run):
+def create_borgmatic_manifest(config, config_paths, borgmatic_runtime_directory, dry_run):
'''
- Create a borgmatic manifest file to store the paths to the configuration files used to create
- the archive.
+ Given a configuration dict, a sequence of config file paths, the borgmatic runtime directory,
+ and whether this is a dry run, create a borgmatic manifest file to store the paths to the
+ configuration files used to create the archive.
'''
if dry_run:
return
- borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
borgmatic_manifest_path = os.path.join(
borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
)
@@ -71,54 +71,68 @@ def run_create(
global_arguments.dry_run,
**hook_context,
)
- logger.info(f'{repository.get("label", repository["path"])}: Creating archive{dry_run_label}')
- borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
- 'remove_data_source_dumps',
- config,
- repository['path'],
- borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
- global_arguments.dry_run,
- )
- active_dumps = borgmatic.hooks.dispatch.call_hooks(
- 'dump_data_sources',
- config,
- repository['path'],
- borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
- global_arguments.dry_run,
- )
- if config.get('store_config_files', True):
- create_borgmatic_manifest(
+
+ log_prefix = repository.get('label', repository['path'])
+ logger.info(f'{log_prefix}: Creating archive{dry_run_label}')
+
+ with borgmatic.config.paths.Runtime_directory(
+ config, log_prefix
+ ) as borgmatic_runtime_directory:
+ borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+ 'remove_data_source_dumps',
config,
- config_paths,
+ repository['path'],
+ borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
global_arguments.dry_run,
)
- stream_processes = [process for processes in active_dumps.values() for process in processes]
+ active_dumps = borgmatic.hooks.dispatch.call_hooks(
+ 'dump_data_sources',
+ config,
+ repository['path'],
+ borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
+ global_arguments.dry_run,
+ )
+ stream_processes = [process for processes in active_dumps.values() for process in processes]
- json_output = borgmatic.borg.create.create_archive(
- global_arguments.dry_run,
- repository['path'],
- config,
- config_paths,
- local_borg_version,
- global_arguments,
- local_path=local_path,
- remote_path=remote_path,
- progress=create_arguments.progress,
- stats=create_arguments.stats,
- json=create_arguments.json,
- list_files=create_arguments.list_files,
- stream_processes=stream_processes,
- )
- if json_output:
- yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+ if config.get('store_config_files', True):
+ create_borgmatic_manifest(
+ config,
+ config_paths,
+ borgmatic_runtime_directory,
+ global_arguments.dry_run,
+ )
+
+ json_output = borgmatic.borg.create.create_archive(
+ global_arguments.dry_run,
+ repository['path'],
+ config,
+ config_paths,
+ local_borg_version,
+ global_arguments,
+ borgmatic_runtime_directory,
+ local_path=local_path,
+ remote_path=remote_path,
+ progress=create_arguments.progress,
+ stats=create_arguments.stats,
+ json=create_arguments.json,
+ list_files=create_arguments.list_files,
+ stream_processes=stream_processes,
+ )
+
+ if json_output:
+ yield borgmatic.actions.json.parse_json(json_output, repository.get('label'))
+
+ borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+ 'remove_data_source_dumps',
+ config,
+ config_filename,
+ borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
+ global_arguments.dry_run,
+ )
- borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
- 'remove_data_source_dumps',
- config,
- config_filename,
- borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
- global_arguments.dry_run,
- )
borgmatic.hooks.command.execute_hook(
config.get('after_backup'),
config.get('umask'),
diff --git a/borgmatic/actions/restore.py b/borgmatic/actions/restore.py
index 3441002c..0010b250 100644
--- a/borgmatic/actions/restore.py
+++ b/borgmatic/actions/restore.py
@@ -108,6 +108,7 @@ def restore_single_data_source(
hook_name,
data_source,
connection_params,
+ borgmatic_runtime_directory,
):
'''
Given (among other things) an archive name, a data source hook name, the hostname, port,
@@ -123,9 +124,9 @@ def restore_single_data_source(
config,
repository['path'],
borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
data_source['name'],
)[hook_name]
- borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
destination_path = (
tempfile.mkdtemp(dir=borgmatic_runtime_directory)
@@ -135,7 +136,7 @@ def restore_single_data_source(
try:
# Kick off a single data source extract. If using a directory format, extract to a temporary
- # directory. Otheriwes extract the single dump file to stdout.
+ # directory. Otherwise extract the single dump file to stdout.
extract_process = borgmatic.borg.extract.extract_archive(
dry_run=global_arguments.dry_run,
repository=repository['path'],
@@ -170,6 +171,7 @@ def restore_single_data_source(
dry_run=global_arguments.dry_run,
extract_process=extract_process,
connection_params=connection_params,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
)
@@ -181,17 +183,17 @@ def collect_archive_data_source_names(
global_arguments,
local_path,
remote_path,
+ borgmatic_runtime_directory,
):
'''
Given a local or remote repository path, a resolved archive name, a configuration dict, the
- local Borg version, global_arguments an argparse.Namespace, and local and remote Borg paths,
- query the archive for the names of data sources it contains as dumps and return them as a dict
- from hook name to a sequence of data source names.
+ local Borg version, global_arguments an argparse.Namespace, local and remote Borg paths, and the
+ borgmatic runtime directory, query the archive for the names of data sources it contains as
+ dumps and return them as a dict from hook name to a sequence of data source names.
'''
borgmatic_source_directory = str(
pathlib.Path(borgmatic.config.paths.get_borgmatic_source_directory(config))
)
- borgmatic_runtime_directory = borgmatic.config.paths.get_borgmatic_runtime_directory(config)
# Probe for the data source dumps in multiple locations, as the default location has moved to
# the borgmatic runtime directory (which get stored as just "/borgmatic" with Borg 1.4+). But we
@@ -207,7 +209,7 @@ def collect_archive_data_source_names(
+ borgmatic.hooks.dump.make_data_source_dump_path(base_directory, '*_databases/*/*')
for base_directory in (
'borgmatic',
- borgmatic_runtime_directory.lstrip('/'),
+ borgmatic.config.paths.make_runtime_directory_glob(borgmatic_runtime_directory),
borgmatic_source_directory.lstrip('/'),
)
],
@@ -342,109 +344,116 @@ def run_restore(
):
return
- logger.info(
- f'{repository.get("label", repository["path"])}: Restoring data sources from archive {restore_arguments.archive}'
- )
+ log_prefix = repository.get('label', repository['path'])
+ logger.info(f'{log_prefix}: Restoring data sources from archive {restore_arguments.archive}')
- borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
- 'remove_data_source_dumps',
- config,
- repository['path'],
- borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
- global_arguments.dry_run,
- )
+ with borgmatic.config.paths.Runtime_directory(
+ config, log_prefix
+ ) as borgmatic_runtime_directory:
+ borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+ 'remove_data_source_dumps',
+ config,
+ repository['path'],
+ borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
+ global_arguments.dry_run,
+ )
- archive_name = borgmatic.borg.repo_list.resolve_archive_name(
- repository['path'],
- restore_arguments.archive,
- config,
- local_borg_version,
- global_arguments,
- local_path,
- remote_path,
- )
- archive_data_source_names = collect_archive_data_source_names(
- repository['path'],
- archive_name,
- config,
- local_borg_version,
- global_arguments,
- local_path,
- remote_path,
- )
- restore_names = find_data_sources_to_restore(
- restore_arguments.data_sources, archive_data_source_names
- )
- found_names = set()
- remaining_restore_names = {}
- connection_params = {
- 'hostname': restore_arguments.hostname,
- 'port': restore_arguments.port,
- 'username': restore_arguments.username,
- 'password': restore_arguments.password,
- 'restore_path': restore_arguments.restore_path,
- }
+ archive_name = borgmatic.borg.repo_list.resolve_archive_name(
+ repository['path'],
+ restore_arguments.archive,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ )
+ archive_data_source_names = collect_archive_data_source_names(
+ repository['path'],
+ archive_name,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ borgmatic_runtime_directory,
+ )
+ restore_names = find_data_sources_to_restore(
+ restore_arguments.data_sources, archive_data_source_names
+ )
+ found_names = set()
+ remaining_restore_names = {}
+ connection_params = {
+ 'hostname': restore_arguments.hostname,
+ 'port': restore_arguments.port,
+ 'username': restore_arguments.username,
+ 'password': restore_arguments.password,
+ 'restore_path': restore_arguments.restore_path,
+ }
- for hook_name, data_source_names in restore_names.items():
- for data_source_name in data_source_names:
- found_hook_name, found_data_source = get_configured_data_source(
- config, archive_data_source_names, hook_name, data_source_name
- )
-
- if not found_data_source:
- remaining_restore_names.setdefault(found_hook_name or hook_name, []).append(
- data_source_name
+ for hook_name, data_source_names in restore_names.items():
+ for data_source_name in data_source_names:
+ found_hook_name, found_data_source = get_configured_data_source(
+ config, archive_data_source_names, hook_name, data_source_name
)
- continue
- found_names.add(data_source_name)
- restore_single_data_source(
- repository,
- config,
- local_borg_version,
- global_arguments,
- local_path,
- remote_path,
- archive_name,
- found_hook_name or hook_name,
- dict(found_data_source, **{'schemas': restore_arguments.schemas}),
- connection_params,
- )
+ if not found_data_source:
+ remaining_restore_names.setdefault(found_hook_name or hook_name, []).append(
+ data_source_name
+ )
+ continue
- # For any data sources that weren't found via exact matches in the configuration, try to
- # fallback to "all" entries.
- for hook_name, data_source_names in remaining_restore_names.items():
- for data_source_name in data_source_names:
- found_hook_name, found_data_source = get_configured_data_source(
- config, archive_data_source_names, hook_name, data_source_name, 'all'
- )
+ found_names.add(data_source_name)
+ restore_single_data_source(
+ repository,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ archive_name,
+ found_hook_name or hook_name,
+ dict(found_data_source, **{'schemas': restore_arguments.schemas}),
+ connection_params,
+ borgmatic_runtime_directory,
+ )
- if not found_data_source:
- continue
+ # For any data sources that weren't found via exact matches in the configuration, try to
+ # fallback to "all" entries.
+ for hook_name, data_source_names in remaining_restore_names.items():
+ for data_source_name in data_source_names:
+ found_hook_name, found_data_source = get_configured_data_source(
+ config, archive_data_source_names, hook_name, data_source_name, 'all'
+ )
- found_names.add(data_source_name)
- data_source = copy.copy(found_data_source)
- data_source['name'] = data_source_name
+ if not found_data_source:
+ continue
- restore_single_data_source(
- repository,
- config,
- local_borg_version,
- global_arguments,
- local_path,
- remote_path,
- archive_name,
- found_hook_name or hook_name,
- dict(data_source, **{'schemas': restore_arguments.schemas}),
- connection_params,
- )
+ found_names.add(data_source_name)
+ data_source = copy.copy(found_data_source)
+ data_source['name'] = data_source_name
- borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
- 'remove_data_source_dumps',
- config,
- repository['path'],
- borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
- global_arguments.dry_run,
- )
+ restore_single_data_source(
+ repository,
+ config,
+ local_borg_version,
+ global_arguments,
+ local_path,
+ remote_path,
+ archive_name,
+ found_hook_name or hook_name,
+ dict(data_source, **{'schemas': restore_arguments.schemas}),
+ connection_params,
+ borgmatic_runtime_directory,
+ )
+
+ borgmatic.hooks.dispatch.call_hooks_even_if_unconfigured(
+ 'remove_data_source_dumps',
+ config,
+ repository['path'],
+ borgmatic.hooks.dump.DATA_SOURCE_HOOK_NAMES,
+ borgmatic_runtime_directory,
+ global_arguments.dry_run,
+ )
ensure_data_sources_found(restore_names, remaining_restore_names, found_names)
diff --git a/borgmatic/borg/create.py b/borgmatic/borg/create.py
index c34f7dfd..9eacf4d2 100644
--- a/borgmatic/borg/create.py
+++ b/borgmatic/borg/create.py
@@ -504,6 +504,7 @@ def create_archive(
config_paths,
local_borg_version,
global_arguments,
+ borgmatic_runtime_directory,
local_path='borg',
remote_path=None,
progress=False,
@@ -524,9 +525,7 @@ def create_archive(
working_directory = borgmatic.config.paths.get_working_directory(config)
borgmatic_runtime_directories = expand_directories(
- collect_borgmatic_runtime_directories(
- borgmatic.config.paths.get_borgmatic_runtime_directory(config)
- ),
+ collect_borgmatic_runtime_directories(borgmatic_runtime_directory),
working_directory=working_directory,
)
diff --git a/borgmatic/config/paths.py b/borgmatic/config/paths.py
index ad318d6d..0b13e65d 100644
--- a/borgmatic/config/paths.py
+++ b/borgmatic/config/paths.py
@@ -1,4 +1,8 @@
+import logging
import os
+import tempfile
+
+logger = logging.getLogger(__name__)
def expand_user_in_path(path):
@@ -26,26 +30,91 @@ def get_borgmatic_source_directory(config):
return expand_user_in_path(config.get('borgmatic_source_directory') or '~/.borgmatic')
-def get_borgmatic_runtime_directory(config):
- '''
- Given a configuration dict, get the borgmatic runtime directory used for storing temporary
- runtime data like streaming database dumps and bootstrap metadata. Defaults to
- $XDG_RUNTIME_DIR/./borgmatic or $TMPDIR/./borgmatic or $TEMP/./borgmatic or
- /run/user/$UID/./borgmatic.
+TEMPORARY_DIRECTORY_PREFIX = 'borgmatic-'
- The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./"
- does not get stored in the file path within an archive. That way, the path of the runtime
- directory can change without leaving database dumps within an archive inaccessible.
+
+class Runtime_directory:
'''
- return expand_user_in_path(
- os.path.join(
+ A Python context manager for creating and cleaning up the borgmatic runtime directory used for
+ storing temporary runtime data like streaming database dumps and bootstrap metadata.
+
+ Example use as a context manager:
+
+ with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
+ do_something_with(borgmatic_runtime_directory)
+
+ For the scope of that "with" statement, the runtime directory is available. Afterwards, it
+ automatically gets cleaned up as necessary.
+ '''
+
+ def __init__(self, config, log_prefix):
+ '''
+ Given a configuration dict and a log prefix, determine the borgmatic runtime directory,
+ creating a secure, temporary directory within it if necessary. Defaults to
+ $XDG_RUNTIME_DIR/./borgmatic or $RUNTIME_DIRECTORY/./borgmatic or
+ $TMPDIR/borgmatic-[random]/./borgmatic or $TEMP/borgmatic-[random]/./borgmatic or
+ /tmp/borgmatic-[random]/./borgmatic where "[random]" is a randomly generated string intended
+ to avoid path collisions.
+
+ If XDG_RUNTIME_DIR or RUNTIME_DIRECTORY is set and already ends in "/borgmatic", then don't
+ tack on a second "/borgmatic" path component.
+
+ The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./"
+ does not get stored in the file path within an archive. That way, the path of the runtime
+ directory can change without leaving database dumps within an archive inaccessible.
+ '''
+ runtime_directory = (
config.get('user_runtime_directory')
- or os.environ.get('XDG_RUNTIME_DIR')
- or os.environ.get('TMPDIR')
- or os.environ.get('TEMP')
- or f'/run/user/{os.getuid()}',
- '.',
- 'borgmatic',
+ or os.environ.get('XDG_RUNTIME_DIR') # Set by PAM on Linux.
+ or os.environ.get('RUNTIME_DIRECTORY') # Set by systemd if configured.
+ )
+
+ if runtime_directory:
+ self.temporary_directory = None
+ else:
+ base_directory = os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp'
+ os.makedirs(base_directory, mode=0o700, exist_ok=True)
+ self.temporary_directory = tempfile.TemporaryDirectory(
+ prefix=TEMPORARY_DIRECTORY_PREFIX,
+ dir=base_directory,
+ )
+ runtime_directory = self.temporary_directory.name
+
+ (base_path, final_directory) = os.path.split(runtime_directory.rstrip(os.path.sep))
+
+ self.runtime_path = expand_user_in_path(
+ os.path.join(
+ base_path if final_directory == 'borgmatic' else runtime_directory, '.', 'borgmatic'
+ )
+ )
+ os.makedirs(self.runtime_path, mode=0o700, exist_ok=True)
+
+ logger.debug(f'{log_prefix}: Using runtime directory {os.path.normpath(self.runtime_path)}')
+
+ def __enter__(self):
+ '''
+ Return the borgmatic runtime path as a string.
+ '''
+ return self.runtime_path
+
+ def __exit__(self, exception, value, traceback):
+ '''
+ Delete any temporary directory that was created as part of initialization.
+ '''
+ if self.temporary_directory:
+ self.temporary_directory.cleanup()
+
+
+def make_runtime_directory_glob(borgmatic_runtime_directory):
+ '''
+ Given a borgmatic runtime directory path, make a glob that would match that path, specifically
+ replacing any randomly generated temporary subdirectory with "*" since such a directory's name
+ changes on every borgmatic run.
+ '''
+ return os.path.join(
+ *(
+ '*' if subdirectory.startswith(TEMPORARY_DIRECTORY_PREFIX) else subdirectory
+ for subdirectory in os.path.normpath(borgmatic_runtime_directory).split(os.path.sep)
)
)
@@ -59,10 +128,9 @@ def get_borgmatic_state_directory(config):
return expand_user_in_path(
os.path.join(
config.get('user_state_directory')
- or os.environ.get(
- 'XDG_STATE_HOME',
- '~/.local/state',
- ),
+ or os.environ.get('XDG_STATE_HOME')
+ or os.environ.get('STATE_DIRECTORY') # Set by systemd if configured.
+ or '~/.local/state',
'borgmatic',
)
)
diff --git a/borgmatic/hooks/mariadb.py b/borgmatic/hooks/mariadb.py
index dcbd9d4b..fa8e9960 100644
--- a/borgmatic/hooks/mariadb.py
+++ b/borgmatic/hooks/mariadb.py
@@ -14,15 +14,11 @@ from borgmatic.hooks import dump
logger = logging.getLogger(__name__)
-def make_dump_path(config, base_directory=None): # pragma: no cover
+def make_dump_path(base_directory): # pragma: no cover
'''
- Given a configuration dict and an optional base directory, make the corresponding dump path. If
- a base directory isn't provided, use the borgmatic runtime directory.
+ Given a base directory, make the corresponding dump path.
'''
- return dump.make_data_source_dump_path(
- base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
- 'mariadb_databases',
- )
+ return dump.make_data_source_dump_path(base_directory, 'mariadb_databases')
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
@@ -126,12 +122,12 @@ def use_streaming(databases, config, log_prefix):
return any(databases)
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Dump the given MariaDB databases to a named pipe. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the given
- configuration dict to construct the destination path and the given log prefix in any log
- entries.
+ borgmatic runtime directory to construct the destination path and the given log prefix in any
+ log entries.
Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -142,7 +138,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
logger.info(f'{log_prefix}: Dumping MariaDB databases{dry_run_label}')
for database in databases:
- dump_path = make_dump_path(config)
+ dump_path = make_dump_path(borgmatic_runtime_directory)
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
dump_database_names = database_names_to_dump(
database, extra_environment, log_prefix, dry_run
@@ -185,43 +181,55 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
return [process for process in processes if process]
-def remove_data_source_dumps(databases, config, log_prefix, dry_run): # pragma: no cover
+def remove_data_source_dumps(
+ databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+): # pragma: no cover
'''
- Remove all database dump files for this hook regardless of the given databases. Use the given
- configuration dict to construct the destination path and the log prefix in any log entries. If
- this is a dry run, then don't actually remove anything.
+ Remove all database dump files for this hook regardless of the given databases. Use the
+ borgmatic_runtime_directory to construct the destination path and the log prefix in any log
+ entries. If this is a dry run, then don't actually remove anything.
'''
- dump.remove_data_source_dumps(make_dump_path(config), 'MariaDB', log_prefix, dry_run)
+ dump.remove_data_source_dumps(
+ make_dump_path(borgmatic_runtime_directory), 'MariaDB', log_prefix, dry_run
+ )
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None): # pragma: no cover
+def make_data_source_dump_patterns(
+ databases, config, log_prefix, borgmatic_runtime_directory, name=None
+): # pragma: no cover
'''
- Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
- database name to match, return the corresponding glob patterns to match the database dump in an
- archive.
+ Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+ borgmatic runtime directory, and a database name to match, return the corresponding glob
+ patterns to match the database dump in an archive.
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
return (
+ dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, 'borgmatic'), name, hostname='*'
+ make_dump_path(borgmatic_runtime_directory), name, hostname='*'
),
- dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+ make_dump_path(borgmatic_source_directory), name, hostname='*'
),
)
def restore_data_source_dump(
- hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+ hook_config,
+ config,
+ log_prefix,
+ data_source,
+ dry_run,
+ extract_process,
+ connection_params,
+ borgmatic_runtime_directory,
):
'''
Restore a database from the given extract stream. The database is supplied as a data source
- configuration dict, but the given hook configuration is ignored. The given configuration dict is
- used to construct the destination path, and the given log prefix is used for any log entries. If
- this is a dry run, then don't actually restore anything. Trigger the given active extract
- process (an instance of subprocess.Popen) to produce output to consume.
+ configuration dict, but the given hook configuration is ignored. The given log prefix is used
+ for any log entries. If this is a dry run, then don't actually restore anything. Trigger the
+ given active extract process (an instance of subprocess.Popen) to produce output to consume.
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
hostname = connection_params['hostname'] or data_source.get(
diff --git a/borgmatic/hooks/mongodb.py b/borgmatic/hooks/mongodb.py
index 498b0638..49075823 100644
--- a/borgmatic/hooks/mongodb.py
+++ b/borgmatic/hooks/mongodb.py
@@ -8,15 +8,11 @@ from borgmatic.hooks import dump
logger = logging.getLogger(__name__)
-def make_dump_path(config, base_directory=None): # pragma: no cover
+def make_dump_path(base_directory): # pragma: no cover
'''
- Given a configuration dict and an optional base directory, make the corresponding dump path. If
- a base directory isn't provided, use the borgmatic runtime directory.
+ Given a base directory, make the corresponding dump path.
'''
- return dump.make_data_source_dump_path(
- base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
- 'mongodb_databases',
- )
+ return dump.make_data_source_dump_path(base_directory, 'mongodb_databases')
def use_streaming(databases, config, log_prefix):
@@ -27,11 +23,12 @@ def use_streaming(databases, config, log_prefix):
return any(database.get('format') != 'directory' for database in databases)
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Dump the given MongoDB databases to a named pipe. The databases are supplied as a sequence of
- dicts, one dict describing each database as per the configuration schema. Use the configuration
- dict to construct the destination path and the given log prefix in any log entries.
+ dicts, one dict describing each database as per the configuration schema. Use the borgmatic
+ runtime directory to construct the destination path (used for the directory format and the given
+ log prefix in any log entries.
Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -44,7 +41,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
for database in databases:
name = database['name']
dump_filename = dump.make_data_source_dump_filename(
- make_dump_path(config), name, database.get('hostname')
+ make_dump_path(borgmatic_runtime_directory), name, database.get('hostname')
)
dump_format = database.get('format', 'archive')
@@ -94,36 +91,49 @@ def build_dump_command(database, dump_filename, dump_format):
)
-def remove_data_source_dumps(databases, config, log_prefix, dry_run): # pragma: no cover
+def remove_data_source_dumps(
+ databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+): # pragma: no cover
'''
- Remove all database dump files for this hook regardless of the given databases. Use the log
- prefix in any log entries. Use the given configuration dict to construct the destination path.
- If this is a dry run, then don't actually remove anything.
+ Remove all database dump files for this hook regardless of the given databases. Use the
+ borgmatic_runtime_directory to construct the destination path and the log prefix in any log
+ entries. If this is a dry run, then don't actually remove anything.
'''
- dump.remove_data_source_dumps(make_dump_path(config), 'MongoDB', log_prefix, dry_run)
+ dump.remove_data_source_dumps(
+ make_dump_path(borgmatic_runtime_directory), 'MongoDB', log_prefix, dry_run
+ )
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None): # pragma: no cover
+def make_data_source_dump_patterns(
+ databases, config, log_prefix, borgmatic_runtime_directory, name=None
+): # pragma: no cover
'''
- Given a sequence of database configurations dicts, a configuration dict, a prefix to log with,
- and a database name to match, return the corresponding glob patterns to match the database dump
- in an archive.
+ Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+ borgmatic runtime directory, and a database name to match, return the corresponding glob
+ patterns to match the database dump in an archive.
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
return (
+ dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, 'borgmatic'), name, hostname='*'
+ make_dump_path(borgmatic_runtime_directory), name, hostname='*'
),
- dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+ make_dump_path(borgmatic_source_directory), name, hostname='*'
),
)
def restore_data_source_dump(
- hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+ hook_config,
+ config,
+ log_prefix,
+ data_source,
+ dry_run,
+ extract_process,
+ connection_params,
+ borgmatic_runtime_directory,
):
'''
Restore a database from the given extract stream. The database is supplied as a data source
@@ -137,7 +147,9 @@ def restore_data_source_dump(
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
dump_filename = dump.make_data_source_dump_filename(
- make_dump_path(config), data_source['name'], data_source.get('hostname')
+ make_dump_path(borgmatic_runtime_directory),
+ data_source['name'],
+ data_source.get('hostname'),
)
restore_command = build_restore_command(
extract_process, data_source, dump_filename, connection_params
diff --git a/borgmatic/hooks/mysql.py b/borgmatic/hooks/mysql.py
index fb72da0f..c8c89280 100644
--- a/borgmatic/hooks/mysql.py
+++ b/borgmatic/hooks/mysql.py
@@ -14,15 +14,11 @@ from borgmatic.hooks import dump
logger = logging.getLogger(__name__)
-def make_dump_path(config, base_directory=None): # pragma: no cover
+def make_dump_path(base_directory): # pragma: no cover
'''
- Given a configuration dict and an optional base directory, make the corresponding dump path. If
- a base directory isn't provided, use the borgmatic runtime directory.
+ Given a base directory, make the corresponding dump path.
'''
- return dump.make_data_source_dump_path(
- base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
- 'mysql_databases',
- )
+ return dump.make_data_source_dump_path(base_directory, 'mysql_databases')
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
@@ -125,11 +121,12 @@ def use_streaming(databases, config, log_prefix):
return any(databases)
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Dump the given MySQL/MariaDB databases to a named pipe. The databases are supplied as a sequence
of dicts, one dict describing each database as per the configuration schema. Use the given
- configuration dict to construct the destination path and the given log prefix in any log entries.
+ borgmatic runtime directory to construct the destination path and the given log prefix in any
+ log entries.
Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -140,7 +137,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
logger.info(f'{log_prefix}: Dumping MySQL databases{dry_run_label}')
for database in databases:
- dump_path = make_dump_path(config)
+ dump_path = make_dump_path(borgmatic_runtime_directory)
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
dump_database_names = database_names_to_dump(
database, extra_environment, log_prefix, dry_run
@@ -183,43 +180,55 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
return [process for process in processes if process]
-def remove_data_source_dumps(databases, config, log_prefix, dry_run): # pragma: no cover
+def remove_data_source_dumps(
+ databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+): # pragma: no cover
'''
- Remove all database dump files for this hook regardless of the given databases. Use the given
- configuration dict to construct the destination path and the log prefix in any log entries. If
- this is a dry run, then don't actually remove anything.
+ Remove all database dump files for this hook regardless of the given databases. Use the
+ borgmatic runtime directory to construct the destination path and the log prefix in any log
+ entries. If this is a dry run, then don't actually remove anything.
'''
- dump.remove_data_source_dumps(make_dump_path(config), 'MySQL', log_prefix, dry_run)
+ dump.remove_data_source_dumps(
+ make_dump_path(borgmatic_runtime_directory), 'MySQL', log_prefix, dry_run
+ )
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None): # pragma: no cover
+def make_data_source_dump_patterns(
+ databases, config, log_prefix, borgmatic_runtime_directory, name=None
+): # pragma: no cover
'''
- Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
- database name to match, return the corresponding glob patterns to match the database dump in an
- archive.
+ Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+ borgmatic runtime directory, and a database name to match, return the corresponding glob
+ patterns to match the database dump in an archive.
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
return (
+ dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, 'borgmatic'), name, hostname='*'
+ make_dump_path(borgmatic_runtime_directory), name, hostname='*'
),
- dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+ make_dump_path(borgmatic_source_directory), name, hostname='*'
),
)
def restore_data_source_dump(
- hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+ hook_config,
+ config,
+ log_prefix,
+ data_source,
+ dry_run,
+ extract_process,
+ connection_params,
+ borgmatic_runtime_directory,
):
'''
Restore a database from the given extract stream. The database is supplied as a data source
- configuration dict, but the given hook configuration is ignored. The given configuration dict is
- used to construct the destination path, and the given log prefix is used for any log entries. If
- this is a dry run, then don't actually restore anything. Trigger the given active extract
- process (an instance of subprocess.Popen) to produce output to consume.
+ configuration dict, but the given hook configuration is ignored. The given log prefix is used
+ for any log entries. If this is a dry run, then don't actually restore anything. Trigger the
+ given active extract process (an instance of subprocess.Popen) to produce output to consume.
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
hostname = connection_params['hostname'] or data_source.get(
diff --git a/borgmatic/hooks/postgresql.py b/borgmatic/hooks/postgresql.py
index 1b510148..4067e09e 100644
--- a/borgmatic/hooks/postgresql.py
+++ b/borgmatic/hooks/postgresql.py
@@ -16,15 +16,11 @@ from borgmatic.hooks import dump
logger = logging.getLogger(__name__)
-def make_dump_path(config, base_directory=None): # pragma: no cover
+def make_dump_path(base_directory): # pragma: no cover
'''
- Given a configuration dict and an optional base directory, make the corresponding dump path. If
- a base directory isn't provided, use the borgmatic runtime directory.
+ Given a base directory, make the corresponding dump path.
'''
- return dump.make_data_source_dump_path(
- base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
- 'postgresql_databases',
- )
+ return dump.make_data_source_dump_path(base_directory, 'postgresql_databases')
def make_extra_environment(database, restore_connection_params=None):
@@ -108,12 +104,12 @@ def use_streaming(databases, config, log_prefix):
return any(database.get('format') != 'directory' for database in databases)
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Dump the given PostgreSQL databases to a named pipe. The databases are supplied as a sequence of
dicts, one dict describing each database as per the configuration schema. Use the given
- configuration dict to construct the destination path and the given log prefix in any log
- entries.
+ borgmatic runtime directory to construct the destination path and the given log prefix in any
+ log entries.
Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -127,7 +123,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
for database in databases:
extra_environment = make_extra_environment(database)
- dump_path = make_dump_path(config)
+ dump_path = make_dump_path(borgmatic_runtime_directory)
dump_database_names = database_names_to_dump(
database, extra_environment, log_prefix, dry_run
)
@@ -210,43 +206,57 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
return processes
-def remove_data_source_dumps(databases, config, log_prefix, dry_run): # pragma: no cover
+def remove_data_source_dumps(
+ databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+): # pragma: no cover
'''
- Remove all database dump files for this hook regardless of the given databases. Use the given
- configuration dict to construct the destination path and the log prefix in any log entries. If
- this is a dry run, then don't actually remove anything.
+ Remove all database dump files for this hook regardless of the given databases. Use the
+ borgmatic runtime directory to construct the destination path and the log prefix in any log
+ entries. If this is a dry run, then don't actually remove anything.
'''
- dump.remove_data_source_dumps(make_dump_path(config), 'PostgreSQL', log_prefix, dry_run)
+ dump.remove_data_source_dumps(
+ make_dump_path(borgmatic_runtime_directory), 'PostgreSQL', log_prefix, dry_run
+ )
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None): # pragma: no cover
+def make_data_source_dump_patterns(
+ databases, config, log_prefix, borgmatic_runtime_directory, name=None
+): # pragma: no cover
'''
- Given a sequence of configurations dicts, a configuration dict, a prefix to log with, and a
- database name to match, return the corresponding glob patterns to match the database dump in an
- archive.
+ Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+ borgmatic runtime directory, and a database name to match, return the corresponding glob
+ patterns to match the database dump in an archive.
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
return (
+ dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, 'borgmatic'), name, hostname='*'
+ make_dump_path(borgmatic_runtime_directory), name, hostname='*'
),
- dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+ make_dump_path(borgmatic_source_directory), name, hostname='*'
),
)
def restore_data_source_dump(
- hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+ hook_config,
+ config,
+ log_prefix,
+ data_source,
+ dry_run,
+ extract_process,
+ connection_params,
+ borgmatic_runtime_directory,
):
'''
Restore a database from the given extract stream. The database is supplied as a data source
- configuration dict, but the given hook configuration is ignored. The given configuration dict is
- used to construct the destination path, and the given log prefix is used for any log entries. If
- this is a dry run, then don't actually restore anything. Trigger the given active extract
- process (an instance of subprocess.Popen) to produce output to consume.
+ configuration dict, but the given hook configuration is ignored. The given borgmatic runtime
+ directory is used to construct the destination path (used for the directory format), and the
+ given log prefix is used for any log entries. If this is a dry run, then don't actually restore
+ anything. Trigger the given active extract process (an instance of subprocess.Popen) to produce
+ output to consume.
If the extract process is None, then restore the dump from the filesystem rather than from an
extract stream.
@@ -267,7 +277,9 @@ def restore_data_source_dump(
all_databases = bool(data_source['name'] == 'all')
dump_filename = dump.make_data_source_dump_filename(
- make_dump_path(config), data_source['name'], data_source.get('hostname')
+ make_dump_path(borgmatic_runtime_directory),
+ data_source['name'],
+ data_source.get('hostname'),
)
psql_command = tuple(
shlex.quote(part) for part in shlex.split(data_source.get('psql_command') or 'psql')
diff --git a/borgmatic/hooks/sqlite.py b/borgmatic/hooks/sqlite.py
index 778a6226..f4ea6a72 100644
--- a/borgmatic/hooks/sqlite.py
+++ b/borgmatic/hooks/sqlite.py
@@ -9,15 +9,11 @@ from borgmatic.hooks import dump
logger = logging.getLogger(__name__)
-def make_dump_path(config, base_directory=None): # pragma: no cover
+def make_dump_path(base_directory): # pragma: no cover
'''
- Given a configuration dict and an optional base directory, make the corresponding dump path. If
- a base directory isn't provided, use the borgmatic runtime directory.
+ Given a base directory, make the corresponding dump path.
'''
- return dump.make_data_source_dump_path(
- base_directory or borgmatic.config.paths.get_borgmatic_runtime_directory(config),
- 'sqlite_databases',
- )
+ return dump.make_data_source_dump_path(base_directory, 'sqlite_databases')
def use_streaming(databases, config, log_prefix):
@@ -28,11 +24,11 @@ def use_streaming(databases, config, log_prefix):
return any(databases)
-def dump_data_sources(databases, config, log_prefix, dry_run):
+def dump_data_sources(databases, config, log_prefix, borgmatic_runtime_directory, dry_run):
'''
Dump the given SQLite databases to a named pipe. The databases are supplied as a sequence of
- configuration dicts, as per the configuration schema. Use the given configuration dict to
- construct the destination path and the given log prefix in any log entries.
+ configuration dicts, as per the configuration schema. Use the given borgmatic runtime directory
+ to construct the destination path and the given log prefix in any log entries.
Return a sequence of subprocess.Popen instances for the dump processes ready to spew to a named
pipe. But if this is a dry run, then don't actually dump anything and return an empty sequence.
@@ -52,7 +48,7 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
f'{log_prefix}: No SQLite database at {database_path}; an empty database will be created and dumped'
)
- dump_path = make_dump_path(config)
+ dump_path = make_dump_path(borgmatic_runtime_directory)
dump_filename = dump.make_data_source_dump_filename(dump_path, database['name'])
if os.path.exists(dump_filename):
@@ -80,43 +76,55 @@ def dump_data_sources(databases, config, log_prefix, dry_run):
return processes
-def remove_data_source_dumps(databases, config, log_prefix, dry_run): # pragma: no cover
+def remove_data_source_dumps(
+ databases, config, log_prefix, borgmatic_runtime_directory, dry_run
+): # pragma: no cover
'''
- Remove the given SQLite database dumps from the filesystem. The databases are supplied as a
- sequence of configuration dicts, as per the configuration schema. Use the given configuration
- dict to construct the destination path and the given log prefix in any log entries. If this is a
- dry run, then don't actually remove anything.
+ Remove all database dump files for this hook regardless of the given databases. Use the
+ borgmatic runtime directory to construct the destination path and the log prefix in any log
+ entries. If this is a dry run, then don't actually remove anything.
'''
- dump.remove_data_source_dumps(make_dump_path(config), 'SQLite', log_prefix, dry_run)
+ dump.remove_data_source_dumps(
+ make_dump_path(borgmatic_runtime_directory), 'SQLite', log_prefix, dry_run
+ )
-def make_data_source_dump_patterns(databases, config, log_prefix, name=None): # pragma: no cover
+def make_data_source_dump_patterns(
+ databases, config, log_prefix, borgmatic_runtime_directory, name=None
+): # pragma: no cover
'''
- Make a pattern that matches the given SQLite databases. The databases are supplied as a sequence
- of configuration dicts, as per the configuration schema.
+ Given a sequence of configurations dicts, a configuration dict, a prefix to log with, the
+ borgmatic runtime directory, and a database name to match, return the corresponding glob
+ patterns to match the database dump in an archive.
'''
borgmatic_source_directory = borgmatic.config.paths.get_borgmatic_source_directory(config)
return (
+ dump.make_data_source_dump_filename(make_dump_path('borgmatic'), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, 'borgmatic'), name, hostname='*'
+ make_dump_path(borgmatic_runtime_directory), name, hostname='*'
),
- dump.make_data_source_dump_filename(make_dump_path(config), name, hostname='*'),
dump.make_data_source_dump_filename(
- make_dump_path(config, borgmatic_source_directory), name, hostname='*'
+ make_dump_path(borgmatic_source_directory), name, hostname='*'
),
)
def restore_data_source_dump(
- hook_config, config, log_prefix, data_source, dry_run, extract_process, connection_params
+ hook_config,
+ config,
+ log_prefix,
+ data_source,
+ dry_run,
+ extract_process,
+ connection_params,
+ borgmatic_runtime_directory,
):
'''
Restore a database from the given extract stream. The database is supplied as a data source
- configuration dict, but the given hook configuration is ignored. The given configuration dict is
- used to construct the destination path, and the given log prefix is used for any log entries. If
- this is a dry run, then don't actually restore anything. Trigger the given active extract
- process (an instance of subprocess.Popen) to produce output to consume.
+ configuration dict, but the given hook configuration is ignored. The given log prefix is used
+ for any log entries. If this is a dry run, then don't actually restore anything. Trigger the
+ given active extract process (an instance of subprocess.Popen) to produce output to consume.
'''
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
database_path = connection_params['restore_path'] or data_source.get(
diff --git a/docs/how-to/backup-your-databases.md b/docs/how-to/backup-your-databases.md
index f2162754..57eb68df 100644
--- a/docs/how-to/backup-your-databases.md
+++ b/docs/how-to/backup-your-databases.md
@@ -63,28 +63,9 @@ dump formats, which can't stream and therefore do consume temporary disk
space. Additionally, prior to borgmatic 1.5.3, all database dumps consumed
temporary disk space.)
-New in version 1.9.0 To support
-this, borgmatic creates temporary streaming database dumps within
-`/run/user/$UID/borgmatic` by default (where `$UID` is the current user's ID).
-To customize the `/run/user/$UID` portion of this path, set the
-`user_runtime_directory` option in borgmatic's configuration. Alternatively,
-set the `XDG_RUNTIME_DIR` environment variable (often already set to
-`/run/user/$UID`).
-
-Prior to version 1.9.0
-borgmatic created temporary streaming database dumps within the `~/.borgmatic`
-directory by default. At that time, the path was configurable by the
-`borgmatic_source_directory` configuration option (now deprecated).
-
-New in version 1.9.1In addition
-to `XDG_RUNTIME_DIR`, borgmatic also uses the `TMPDIR` or `TEMP` environment
-variable if set. `TMPDIR` is available on macOS, while `TEMP` is often
-available on other platforms.
-
-Also note that using a database hook implicitly enables the
-`read_special` configuration option (even if it's disabled in your
-configuration) to support this dump and restore streaming. See Limitations
-below for more on this.
+Also note that using a database hook implicitly enables the `read_special`
+configuration option (even if it's disabled in your configuration) to support
+this dump and restore streaming. See Limitations below for more on this.
Here's a more involved example that connects to remote databases:
@@ -132,6 +113,40 @@ additional customization of the options passed to database commands (when
listing databases, restoring databases, etc.).
+### Runtime directory
+
+New in version 1.9.0 To support
+streaming database dumps to Borg, borgmatic uses a runtime directory for
+temporary file storage, probing the following locations (in order) to find it:
+
+ 1. The `user_runtime_directory` borgmatic configuration option.
+ 2. The `XDG_RUNTIME_DIR` environment variable, usually `/run/user/$UID`
+ (where `$UID` is the current user's ID), automatically set by PAM on Linux
+ for a user with a session.
+ 3. New in version 1.9.2The
+ `RUNTIME_DIRECTORY` environment variable, set by systemd if
+ `RuntimeDirectory=borgmatic` is added to borgmatic's systemd service file.
+ 4. New in version 1.9.1The
+ `TMPDIR` environment variable, set on macOS for a user with a session,
+ among other operating systems.
+ 5. New in version 1.9.1The
+ `TEMP` environment variable, set on various systems.
+ 6. New in version 1.9.2
+ Hard-coded `/tmp`. Prior to
+ version 1.9.2This was instead hard-coded to `/run/user/$UID`.
+
+Regardless of the runtime directory selected, borgmatic stores its files
+within a `borgmatic` subdirectory of the runtime directory. Additionally, in
+the case of `TMPDIR`, `TEMP`, and the hard-coded `/tmp`, borgmatic creates a
+randomly named subdirectory in an effort to reduce path collisions in shared
+system temporary directories.
+
+Prior to version 1.9.0
+borgmatic created temporary streaming database dumps within the `~/.borgmatic`
+directory by default. At that time, the path was configurable by the
+`borgmatic_source_directory` configuration option (now deprecated).
+
+
### All databases
If you want to dump all databases on a host, use `all` for the database name:
diff --git a/docs/how-to/deal-with-very-large-backups.md b/docs/how-to/deal-with-very-large-backups.md
index fbef5912..edbfa86a 100644
--- a/docs/how-to/deal-with-very-large-backups.md
+++ b/docs/how-to/deal-with-very-large-backups.md
@@ -240,6 +240,11 @@ this frequency logic, borgmatic records check timestamps within the
portion of this path, set the `user_state_directory` configuration option.
Alternatively, set the `XDG_STATE_HOME` environment variable.
+New in version 1.9.2The
+`STATE_DIRECTORY` environment variable also works for this purpose. It's set
+by systemd if `StateDirectory=borgmatic` is added to borgmatic's systemd
+service file.
+
Prior to version 1.9.0
borgmatic recorded check timestamps within the `~/.borgmatic` directory. At
that time, the path was configurable by the `borgmatic_source_directory`
diff --git a/docs/how-to/extract-a-backup.md b/docs/how-to/extract-a-backup.md
index cc68824d..f6909110 100644
--- a/docs/how-to/extract-a-backup.md
+++ b/docs/how-to/extract-a-backup.md
@@ -156,12 +156,19 @@ borgmatic umount --mount-point /mnt
## Extract the configuration files used to create an archive
-New in version 1.7.15 borgmatic
-automatically stores all the configuration files used to create an archive
-inside the archive itself. They are stored in the archive using their full
-paths from the machine being backed up. This is useful in cases where you've
-lost a configuration file or you want to see what configurations were used to
-create a particular archive.
+New in version 1.7.15 As part
+of creating a backup archive, borgmatic automatically includes all of the
+configuration files used when creating it, storing them inside the archive
+itself with their full paths from the machine being backed up. This is useful
+in cases where you've lost a configuration file or you want to see what
+configurations were used to create a particular archive.
+
+To support this, borgmatic creates a manifest file that records the paths of
+all the borgmatic configuration files stored within an archive. The file gets
+written to borgmatic's runtime directory on disk and then stored within the
+archive. See the [runtime directory
+documentation](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/#runtime-directory)
+for how and where that happens.
To extract the configuration files from an archive, use the `config bootstrap`
action. For example:
diff --git a/docs/how-to/inspect-your-backups.md b/docs/how-to/inspect-your-backups.md
index 8b3676db..e28dc807 100644
--- a/docs/how-to/inspect-your-backups.md
+++ b/docs/how-to/inspect-your-backups.md
@@ -119,10 +119,10 @@ archive, regardless of the user who performs the backup. (Note that Borg
doesn't store the leading `/`.)
With Borg version 1.2 and
-earlierDatabase dump files are stored at `/var/run/$UID/borgmatic`
-(where `$UID` is the current user's ID) unless overridden by the
-`user_runtime_directory` configuration option or the `XDG_STATE_HOME`
-environment variable.
+earlierDatabase dump files are stored at a path dependent on the
+[runtime
+directory](https://torsion.org/borgmatic/docs/how-to/backup-your-databases/#runtime-directory)
+in use at the time the archive was created.
Prior to borgmatic version
1.9.0Database dump files were instead stored at `~/.borgmatic` within
diff --git a/sample/systemd/borgmatic.service b/sample/systemd/borgmatic.service
index 7a96b834..564cbe99 100644
--- a/sample/systemd/borgmatic.service
+++ b/sample/systemd/borgmatic.service
@@ -9,6 +9,8 @@ Documentation=https://torsion.org/borgmatic/
[Service]
Type=oneshot
+RuntimeDirectory=borgmatic
+StateDirectory=borgmatic
# Load single encrypted credential.
LoadCredentialEncrypted=borgmatic.pw
diff --git a/tests/unit/actions/config/test_bootstrap.py b/tests/unit/actions/config/test_bootstrap.py
index dceabe19..7a21e715 100644
--- a/tests/unit/actions/config/test_bootstrap.py
+++ b/tests/unit/actions/config/test_bootstrap.py
@@ -16,9 +16,6 @@ def test_get_config_paths_returns_list_of_config_paths():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/source')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/runtime')
flexmock(module).should_receive('make_bootstrap_config').and_return({})
bootstrap_arguments = flexmock(
repository='repo',
@@ -33,6 +30,12 @@ def test_get_config_paths_returns_list_of_config_paths():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@@ -47,13 +50,61 @@ def test_get_config_paths_returns_list_of_config_paths():
) == ['/borgmatic/config.yaml']
+def test_get_config_paths_probes_for_manifest():
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'get_borgmatic_source_directory'
+ ).and_return('/source')
+ flexmock(module).should_receive('make_bootstrap_config').and_return({})
+ bootstrap_arguments = flexmock(
+ repository='repo',
+ archive='archive',
+ ssh_command=None,
+ local_path='borg7',
+ remote_path='borg8',
+ borgmatic_source_directory=None,
+ user_runtime_directory=None,
+ )
+ global_arguments = flexmock(
+ dry_run=False,
+ )
+ local_borg_version = flexmock()
+ borgmatic_runtime_directory = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ borgmatic_runtime_directory,
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
+ flexmock(module.os.path).should_receive('join').with_args(
+ 'borgmatic', 'bootstrap', 'manifest.json'
+ ).and_return('borgmatic/bootstrap/manifest.json').once()
+ flexmock(module.os.path).should_receive('join').with_args(
+ borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
+ ).and_return('run/borgmatic/bootstrap/manifest.json').once()
+ flexmock(module.os.path).should_receive('join').with_args(
+ '/source', 'bootstrap', 'manifest.json'
+ ).and_return('/source/bootstrap/manifest.json').once()
+ manifest_missing_extract_process = flexmock(
+ stdout=flexmock(read=lambda: None),
+ )
+ manifest_found_extract_process = flexmock(
+ stdout=flexmock(
+ read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
+ ),
+ )
+ flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
+ manifest_missing_extract_process
+ ).and_return(manifest_missing_extract_process).and_return(manifest_found_extract_process)
+
+ assert module.get_config_paths(
+ 'archive', bootstrap_arguments, global_arguments, local_borg_version
+ ) == ['/borgmatic/config.yaml']
+
+
def test_get_config_paths_translates_ssh_command_argument_to_config():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/source')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/runtime')
config = flexmock()
flexmock(module).should_receive('make_bootstrap_config').and_return(config)
bootstrap_arguments = flexmock(
@@ -69,6 +120,12 @@ def test_get_config_paths_translates_ssh_command_argument_to_config():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@@ -96,9 +153,6 @@ def test_get_config_paths_with_missing_manifest_raises_value_error():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/source')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/runtime')
flexmock(module).should_receive('make_bootstrap_config').and_return({})
bootstrap_arguments = flexmock(
repository='repo',
@@ -113,6 +167,13 @@ def test_get_config_paths_with_missing_manifest_raises_value_error():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
+ flexmock(module.os.path).should_receive('join').and_return('run/borgmatic')
extract_process = flexmock(stdout=flexmock(read=lambda: ''))
flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
extract_process
@@ -128,9 +189,6 @@ def test_get_config_paths_with_broken_json_raises_value_error():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/source')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/runtime')
flexmock(module).should_receive('make_bootstrap_config').and_return({})
bootstrap_arguments = flexmock(
repository='repo',
@@ -145,6 +203,12 @@ def test_get_config_paths_with_broken_json_raises_value_error():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(read=lambda: '{"config_paths": ["/oops'),
)
@@ -162,9 +226,6 @@ def test_get_config_paths_with_json_missing_key_raises_value_error():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/source')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/runtime')
flexmock(module).should_receive('make_bootstrap_config').and_return({})
bootstrap_arguments = flexmock(
repository='repo',
@@ -179,6 +240,12 @@ def test_get_config_paths_with_json_missing_key_raises_value_error():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(read=lambda: '{}'),
)
@@ -210,6 +277,12 @@ def test_run_bootstrap_does_not_raise():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
@@ -244,6 +317,12 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config():
dry_run=False,
)
local_borg_version = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
diff --git a/tests/unit/actions/test_check.py b/tests/unit/actions/test_check.py
index f8987733..e7299522 100644
--- a/tests/unit/actions/test_check.py
+++ b/tests/unit/actions/test_check.py
@@ -713,9 +713,6 @@ def test_collect_spot_check_archive_paths_excludes_directories():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/home/user/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/1001/borgmatic')
flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
(
'f /etc/path',
@@ -732,6 +729,7 @@ def test_collect_spot_check_archive_paths_excludes_directories():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/user/1001/borgmatic',
) == ('/etc/path', '/etc/other')
@@ -739,9 +737,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
(
'f /etc/path',
@@ -757,6 +752,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/user/0/borgmatic',
) == ('/etc/path',)
@@ -764,9 +760,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_dire
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
(
'f /etc/path',
@@ -782,6 +775,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_source_dire
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/user/0/borgmatic',
) == ('/etc/path',)
@@ -789,9 +783,6 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.borg.list).should_receive('capture_archive_listing').and_return(
(
'f /etc/path',
@@ -807,6 +798,7 @@ def test_collect_spot_check_archive_paths_excludes_file_in_borgmatic_runtime_dir
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/user/0/borgmatic',
) == ('/etc/path',)
@@ -887,7 +879,7 @@ def test_compare_spot_check_hashes_returns_paths_having_failing_hashes():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar', '/baz', '/quux'),
) == ('/bar',)
@@ -928,7 +920,7 @@ def test_compare_spot_check_hashes_handles_data_sample_percentage_above_100():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar'),
) == ('/foo', '/bar')
@@ -966,7 +958,7 @@ def test_compare_spot_check_hashes_uses_xxh64sum_command_option():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar', '/baz', '/quux'),
) == ('/bar',)
@@ -1003,7 +995,7 @@ def test_compare_spot_check_hashes_considers_path_missing_from_archive_as_not_ma
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar', '/baz', '/quux'),
) == ('/bar',)
@@ -1039,7 +1031,7 @@ def test_compare_spot_check_hashes_considers_non_existent_path_as_not_matching()
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar', '/baz', '/quux'),
) == ('/bar',)
@@ -1086,7 +1078,7 @@ def test_compare_spot_check_hashes_with_too_many_paths_feeds_them_to_commands_in
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('/foo', '/bar', '/baz', '/quux'),
) == ('/quux',)
@@ -1129,7 +1121,7 @@ def test_compare_spot_check_hashes_uses_working_directory_to_access_source_paths
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
- log_label='repo',
+ log_prefix='repo',
source_paths=('foo', 'bar', 'baz', 'quux'),
) == ('bar',)
@@ -1149,6 +1141,7 @@ def test_spot_check_without_spot_configuration_errors():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1161,6 +1154,7 @@ def test_spot_check_without_any_configuration_errors():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1181,6 +1175,7 @@ def test_spot_check_data_tolerance_percenatge_greater_than_data_sample_percentag
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1212,6 +1207,7 @@ def test_spot_check_with_count_delta_greater_than_count_tolerance_percentage_err
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1244,6 +1240,7 @@ def test_spot_check_with_failing_percentage_greater_than_data_tolerance_percenta
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1275,6 +1272,7 @@ def test_spot_check_with_high_enough_tolerances_does_not_raise():
global_arguments=flexmock(),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1362,6 +1360,9 @@ def test_run_check_runs_configured_spot_check():
flexmock(module).should_receive('make_archives_check_id').and_return(None)
flexmock(module).should_receive('filter_checks_on_frequency').and_return({'spot'})
flexmock(module.borgmatic.borg.check).should_receive('check_archives').never()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
flexmock(module.borgmatic.actions.check).should_receive('spot_check').once()
flexmock(module).should_receive('make_check_time_path')
flexmock(module).should_receive('write_check_time')
diff --git a/tests/unit/actions/test_create.py b/tests/unit/actions/test_create.py
index 8582abae..c8450897 100644
--- a/tests/unit/actions/test_create.py
+++ b/tests/unit/actions/test_create.py
@@ -8,6 +8,9 @@ from borgmatic.actions import create as module
def test_run_create_executes_and_calls_hooks_for_configured_repository():
flexmock(module.logger).answer = lambda message: None
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
flexmock(module).should_receive('create_borgmatic_manifest').once()
flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -44,6 +47,9 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
def test_run_create_with_store_config_files_false_does_not_create_borgmatic_manifest():
flexmock(module.logger).answer = lambda message: None
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').never()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
flexmock(module).should_receive('create_borgmatic_manifest').never()
flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -82,6 +88,9 @@ def test_run_create_runs_with_selected_repository():
flexmock(module.borgmatic.config.validate).should_receive(
'repositories_match'
).once().and_return(True)
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
flexmock(module).should_receive('create_borgmatic_manifest').once()
flexmock(module.borgmatic.hooks.command).should_receive('execute_hook').times(2)
@@ -120,6 +129,7 @@ def test_run_create_bails_if_repository_does_not_match():
flexmock(module.borgmatic.config.validate).should_receive(
'repositories_match'
).once().and_return(False)
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
flexmock(module).should_receive('create_borgmatic_manifest').never()
create_arguments = flexmock(
@@ -153,6 +163,9 @@ def test_run_create_produces_json():
flexmock(module.borgmatic.config.validate).should_receive(
'repositories_match'
).once().and_return(True)
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
flexmock(module.borgmatic.borg.create).should_receive('create_archive').once().and_return(
flexmock()
)
@@ -191,18 +204,15 @@ def test_run_create_produces_json():
def test_create_borgmatic_manifest_creates_manifest_file():
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.os.path).should_receive('join').with_args(
- '/run/user/0/borgmatic', 'bootstrap', 'manifest.json'
- ).and_return('/run/user/0/borgmatic/bootstrap/manifest.json')
+ '/run/borgmatic', 'bootstrap', 'manifest.json'
+ ).and_return('/run/borgmatic/bootstrap/manifest.json')
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.os).should_receive('makedirs').and_return(True)
flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
flexmock(sys.modules['builtins']).should_receive('open').with_args(
- '/run/user/0/borgmatic/bootstrap/manifest.json', 'w'
+ '/run/borgmatic/bootstrap/manifest.json', 'w'
).and_return(
flexmock(
__enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
@@ -211,22 +221,19 @@ def test_create_borgmatic_manifest_creates_manifest_file():
)
flexmock(module.json).should_receive('dump').and_return(True).once()
- module.create_borgmatic_manifest({}, 'test.yaml', False)
+ module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', False)
def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_runtime_directory():
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/borgmatic')
flexmock(module.os.path).should_receive('join').with_args(
- '/borgmatic', 'bootstrap', 'manifest.json'
- ).and_return('/borgmatic/bootstrap/manifest.json')
+ '/run/borgmatic', 'bootstrap', 'manifest.json'
+ ).and_return('/run/borgmatic/bootstrap/manifest.json')
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.os).should_receive('makedirs').and_return(True)
flexmock(module.importlib.metadata).should_receive('version').and_return('1.0.0')
flexmock(sys.modules['builtins']).should_receive('open').with_args(
- '/borgmatic/bootstrap/manifest.json', 'w'
+ '/run/borgmatic/bootstrap/manifest.json', 'w'
).and_return(
flexmock(
__enter__=lambda *args: flexmock(write=lambda *args: None, close=lambda *args: None),
@@ -236,9 +243,11 @@ def test_create_borgmatic_manifest_creates_manifest_file_with_custom_borgmatic_r
flexmock(module.json).should_receive('dump').and_return(True).once()
module.create_borgmatic_manifest(
- {'borgmatic_runtime_directory': '/borgmatic'}, 'test.yaml', False
+ {'borgmatic_runtime_directory': '/borgmatic'}, 'test.yaml', '/run/borgmatic', False
)
def test_create_borgmatic_manifest_does_not_create_manifest_file_on_dry_run():
- module.create_borgmatic_manifest({}, 'test.yaml', True)
+ flexmock(module.json).should_receive('dump').never()
+
+ module.create_borgmatic_manifest({}, 'test.yaml', '/run/borgmatic', True)
diff --git a/tests/unit/actions/test_restore.py b/tests/unit/actions/test_restore.py
index d9e3d9d6..e12c392a 100644
--- a/tests/unit/actions/test_restore.py
+++ b/tests/unit/actions/test_restore.py
@@ -87,11 +87,8 @@ def test_strip_path_prefix_from_extracted_dump_destination_renames_first_matchin
def test_restore_single_data_source_extracts_and_restores_single_file_dump():
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
- 'make_data_source_dump_patterns', object, object, object, object
+ 'make_data_source_dump_patterns', object, object, object, object, object
).and_return({'postgresql': flexmock()})
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.tempfile).should_receive('mkdtemp').never()
flexmock(module.borgmatic.hooks.dump).should_receive(
'convert_glob_patterns_to_borg_pattern'
@@ -110,6 +107,7 @@ def test_restore_single_data_source_extracts_and_restores_single_file_dump():
dry_run=object,
extract_process=object,
connection_params=object,
+ borgmatic_runtime_directory=object,
).once()
module.restore_single_data_source(
@@ -123,16 +121,14 @@ def test_restore_single_data_source_extracts_and_restores_single_file_dump():
hook_name='postgresql',
data_source={'name': 'test', 'format': 'plain'},
connection_params=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
def test_restore_single_data_source_extracts_and_restores_directory_dump():
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
- 'make_data_source_dump_patterns', object, object, object, object
+ 'make_data_source_dump_patterns', object, object, object, object, object
).and_return({'postgresql': flexmock()})
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
'/run/user/0/borgmatic/tmp1234'
)
@@ -153,6 +149,7 @@ def test_restore_single_data_source_extracts_and_restores_directory_dump():
dry_run=object,
extract_process=object,
connection_params=object,
+ borgmatic_runtime_directory='/run/borgmatic',
).once()
module.restore_single_data_source(
@@ -166,16 +163,14 @@ def test_restore_single_data_source_extracts_and_restores_directory_dump():
hook_name='postgresql',
data_source={'name': 'test', 'format': 'directory'},
connection_params=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporary_directory():
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
- 'make_data_source_dump_patterns', object, object, object, object
+ 'make_data_source_dump_patterns', object, object, object, object, object
).and_return({'postgresql': flexmock()})
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
'/run/user/0/borgmatic/tmp1234'
)
@@ -196,6 +191,7 @@ def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporar
dry_run=object,
extract_process=object,
connection_params=object,
+ borgmatic_runtime_directory='/run/user/0/borgmatic/tmp1234',
).never()
with pytest.raises(ValueError):
@@ -210,19 +206,15 @@ def test_restore_single_data_source_with_directory_dump_error_cleans_up_temporar
hook_name='postgresql',
data_source={'name': 'test', 'format': 'directory'},
connection_params=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_directory_move_and_cleanup():
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').with_args(
- 'make_data_source_dump_patterns', object, object, object, object
+ 'make_data_source_dump_patterns', object, object, object, object, object
).and_return({'postgresql': flexmock()})
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
- flexmock(module.tempfile).should_receive('mkdtemp').once().and_return(
- '/run/user/0/borgmatic/tmp1234'
- )
+ flexmock(module.tempfile).should_receive('mkdtemp').once().and_return('/run/borgmatic/tmp1234')
flexmock(module.borgmatic.hooks.dump).should_receive(
'convert_glob_patterns_to_borg_pattern'
).and_return(flexmock())
@@ -240,6 +232,7 @@ def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_direct
dry_run=object,
extract_process=object,
connection_params=object,
+ borgmatic_runtime_directory='/run/borgmatic',
).once()
module.restore_single_data_source(
@@ -253,6 +246,7 @@ def test_restore_single_data_source_with_directory_dump_and_dry_run_skips_direct
hook_name='postgresql',
data_source={'name': 'test', 'format': 'directory'},
connection_params=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -260,9 +254,6 @@ def test_collect_archive_data_source_names_parses_archive_paths():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
''
)
@@ -282,6 +273,7 @@ def test_collect_archive_data_source_names_parses_archive_paths():
global_arguments=flexmock(log_json=False),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
assert archive_data_source_names == {
@@ -294,9 +286,6 @@ def test_collect_archive_data_source_names_parses_archive_paths_with_different_b
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
''
)
@@ -317,6 +306,7 @@ def test_collect_archive_data_source_names_parses_archive_paths_with_different_b
global_arguments=flexmock(log_json=False),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
assert archive_data_source_names == {
@@ -329,9 +319,6 @@ def test_collect_archive_data_source_names_parses_directory_format_archive_paths
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
''
)
@@ -350,6 +337,7 @@ def test_collect_archive_data_source_names_parses_directory_format_archive_paths
global_arguments=flexmock(log_json=False),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
assert archive_data_source_names == {
@@ -361,9 +349,6 @@ def test_collect_archive_data_source_names_skips_bad_archive_paths():
flexmock(module.borgmatic.config.paths).should_receive(
'get_borgmatic_source_directory'
).and_return('/root/.borgmatic')
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/run/user/0/borgmatic')
flexmock(module.borgmatic.hooks.dump).should_receive('make_data_source_dump_path').and_return(
''
)
@@ -384,6 +369,7 @@ def test_collect_archive_data_source_names_skips_bad_archive_paths():
global_arguments=flexmock(log_json=False),
local_path=flexmock(),
remote_path=flexmock(),
+ borgmatic_runtime_directory='/run/borgmatic',
)
assert archive_data_source_names == {
@@ -481,6 +467,13 @@ def test_run_restore_restores_each_data_source():
}
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+ borgmatic_runtime_directory = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ borgmatic_runtime_directory
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
flexmock()
@@ -501,6 +494,7 @@ def test_run_restore_restores_each_data_source():
hook_name='postgresql_databases',
data_source={'name': 'foo', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('restore_single_data_source').with_args(
repository=object,
@@ -513,6 +507,7 @@ def test_run_restore_restores_each_data_source():
hook_name='postgresql_databases',
data_source={'name': 'bar', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('ensure_data_sources_found')
@@ -541,6 +536,12 @@ def test_run_restore_bails_for_non_matching_repository():
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(
False
)
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ flexmock()
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
flexmock(module.borgmatic.hooks.dispatch).should_receive(
'call_hooks_even_if_unconfigured'
).never()
@@ -563,6 +564,13 @@ def test_run_restore_restores_data_source_configured_with_all_name():
}
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+ borgmatic_runtime_directory = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ borgmatic_runtime_directory
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
flexmock()
@@ -599,6 +607,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
hook_name='postgresql_databases',
data_source={'name': 'foo', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('restore_single_data_source').with_args(
repository=object,
@@ -611,6 +620,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
hook_name='postgresql_databases',
data_source={'name': 'bar', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('ensure_data_sources_found')
@@ -641,6 +651,13 @@ def test_run_restore_skips_missing_data_source():
}
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+ borgmatic_runtime_directory = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ borgmatic_runtime_directory
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
flexmock()
@@ -677,6 +694,7 @@ def test_run_restore_skips_missing_data_source():
hook_name='postgresql_databases',
data_source={'name': 'foo', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('restore_single_data_source').with_args(
repository=object,
@@ -689,6 +707,7 @@ def test_run_restore_skips_missing_data_source():
hook_name='postgresql_databases',
data_source={'name': 'bar', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).never()
flexmock(module).should_receive('ensure_data_sources_found')
@@ -720,6 +739,13 @@ def test_run_restore_restores_data_sources_from_different_hooks():
}
flexmock(module.borgmatic.config.validate).should_receive('repositories_match').and_return(True)
+ borgmatic_runtime_directory = flexmock()
+ flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
+ borgmatic_runtime_directory
+ )
+ flexmock(module.borgmatic.config.paths).should_receive(
+ 'make_runtime_directory_glob'
+ ).replace_with(lambda path: path)
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks_even_if_unconfigured')
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
flexmock()
@@ -749,6 +775,7 @@ def test_run_restore_restores_data_sources_from_different_hooks():
hook_name='postgresql_databases',
data_source={'name': 'foo', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('restore_single_data_source').with_args(
repository=object,
@@ -761,6 +788,7 @@ def test_run_restore_restores_data_sources_from_different_hooks():
hook_name='mysql_databases',
data_source={'name': 'bar', 'schemas': None},
connection_params=object,
+ borgmatic_runtime_directory=borgmatic_runtime_directory,
).once()
flexmock(module).should_receive('ensure_data_sources_found')
diff --git a/tests/unit/borg/test_create.py b/tests/unit/borg/test_create.py
index f24707a5..b1f8b509 100644
--- a/tests/unit/borg/test_create.py
+++ b/tests/unit/borg/test_create.py
@@ -1453,9 +1453,6 @@ def test_create_archive_calls_borg_with_parameters():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1483,6 +1480,7 @@ def test_create_archive_calls_borg_with_parameters():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1490,9 +1488,6 @@ def test_create_archive_calls_borg_with_environment():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1521,6 +1516,7 @@ def test_create_archive_calls_borg_with_environment():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1528,9 +1524,6 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1559,6 +1552,7 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1566,9 +1560,6 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1595,6 +1586,7 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
json=True,
)
@@ -1603,9 +1595,6 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1634,6 +1623,7 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1641,9 +1631,6 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1670,6 +1657,7 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
json=True,
)
@@ -1680,9 +1668,6 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create', '--dry-run'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1711,6 +1696,7 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
stats=True,
)
@@ -1719,9 +1705,6 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1752,6 +1735,7 @@ def test_create_archive_with_working_directory_calls_borg_with_working_directory
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1759,9 +1743,6 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1791,6 +1772,7 @@ def test_create_archive_with_exit_codes_calls_borg_using_them():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
@@ -1798,9 +1780,6 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1828,6 +1807,7 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_answer_ou
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
stats=True,
)
@@ -1836,9 +1816,6 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(
@@ -1871,6 +1848,7 @@ def test_create_archive_with_files_calls_borg_with_answer_output_log_level():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
list_files=True,
)
@@ -1879,9 +1857,6 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1910,6 +1885,7 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
progress=True,
)
@@ -1918,9 +1894,6 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -1948,6 +1921,7 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
progress=True,
)
@@ -1957,9 +1931,6 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
processes = flexmock()
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(
@@ -2009,6 +1980,7 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
progress=True,
stream_processes=processes,
)
@@ -2018,9 +1990,6 @@ def test_create_archive_with_json_calls_borg_with_json_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2046,6 +2015,7 @@ def test_create_archive_with_json_calls_borg_with_json_flag():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
json=True,
)
@@ -2056,9 +2026,6 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2084,6 +2051,7 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
json=True,
stats=True,
)
@@ -2095,9 +2063,6 @@ def test_create_archive_calls_borg_with_working_directory():
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
flexmock(module).should_receive('expand_directories').and_return(())
- flexmock(module.borgmatic.config.paths).should_receive(
- 'get_borgmatic_runtime_directory'
- ).and_return('/var/run/0/borgmatic')
flexmock(module).should_receive('collect_borgmatic_runtime_directories').and_return([])
flexmock(module).should_receive('make_base_create_command').and_return(
(('borg', 'create'), REPO_ARCHIVE_WITH_PATHS, flexmock(), flexmock())
@@ -2128,6 +2093,7 @@ def test_create_archive_calls_borg_with_working_directory():
config_paths=['/tmp/test.yaml'],
local_borg_version='1.2.3',
global_arguments=flexmock(log_json=False),
+ borgmatic_runtime_directory='/borgmatic/run',
)
diff --git a/tests/unit/config/test_paths.py b/tests/unit/config/test_paths.py
index deeac41b..36a8fb50 100644
--- a/tests/unit/config/test_paths.py
+++ b/tests/unit/config/test_paths.py
@@ -1,3 +1,4 @@
+import pytest
from flexmock import flexmock
from borgmatic.config import paths as module
@@ -33,53 +34,141 @@ def test_get_borgmatic_source_directory_without_config_option_uses_default():
assert module.get_borgmatic_source_directory({}) == '~/.borgmatic'
-def test_get_borgmatic_runtime_directory_uses_config_option():
+def test_runtime_directory_uses_config_option():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os).should_receive('makedirs')
+ config = {'user_runtime_directory': '/run', 'borgmatic_source_directory': '/nope'}
- assert (
- module.get_borgmatic_runtime_directory(
- {'user_runtime_directory': '/tmp', 'borgmatic_source_directory': '/nope'}
- )
- == '/tmp/./borgmatic'
- )
+ with module.Runtime_directory(config, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
-def test_get_borgmatic_runtime_directory_falls_back_to_linux_environment_variable():
+def test_runtime_directory_uses_config_option_without_adding_duplicate_borgmatic_subdirectory():
+ flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os).should_receive('makedirs')
+ config = {'user_runtime_directory': '/run/borgmatic', 'borgmatic_source_directory': '/nope'}
+
+ with module.Runtime_directory(config, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_xdg_runtime_dir():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
- '/tmp'
+ '/run'
)
+ flexmock(module.os).should_receive('makedirs')
- assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
-def test_get_borgmatic_runtime_directory_falls_back_to_macos_environment_variable():
+def test_runtime_directory_falls_back_to_xdg_runtime_dir_without_adding_duplicate_borgmatic_subdirectory():
+ flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
+ '/run/borgmatic'
+ )
+ flexmock(module.os).should_receive('makedirs')
+
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_runtime_directory():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
- flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/tmp')
+ flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+ '/run'
+ )
+ flexmock(module.os).should_receive('makedirs')
- assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
-def test_get_borgmatic_runtime_directory_falls_back_to_other_environment_variable():
+def test_runtime_directory_falls_back_to_runtime_directory_without_adding_duplicate_borgmatic_subdirectory():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+ '/run/borgmatic'
+ )
+ flexmock(module.os).should_receive('makedirs')
+
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up():
+ flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+ None
+ )
+ flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/run')
+ temporary_directory = flexmock(name='/run/borgmatic-1234')
+ temporary_directory.should_receive('cleanup').once()
+ flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+ prefix='borgmatic-', dir='/run'
+ ).and_return(temporary_directory)
+ flexmock(module.os).should_receive('makedirs')
+
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
+
+
+def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up():
+ flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+ None
+ )
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
- flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/tmp')
+ flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/run')
+ temporary_directory = flexmock(name='/run/borgmatic-1234')
+ temporary_directory.should_receive('cleanup').once()
+ flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+ prefix='borgmatic-', dir='/run'
+ ).and_return(temporary_directory)
+ flexmock(module.os).should_receive('makedirs')
- assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
-def test_get_borgmatic_runtime_directory_defaults_to_hard_coded_path():
+def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
- flexmock(module.os.environ).should_receive('get').and_return('/run/user/0')
- flexmock(module.os).should_receive('getuid').and_return(0)
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
+ None
+ )
+ flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None)
+ temporary_directory = flexmock(name='/tmp/borgmatic-1234')
+ temporary_directory.should_receive('cleanup').once()
+ flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
+ prefix='borgmatic-', dir='/tmp'
+ ).and_return(temporary_directory)
+ flexmock(module.os).should_receive('makedirs')
- assert module.get_borgmatic_runtime_directory({}) == '/run/user/0/./borgmatic'
+ with module.Runtime_directory({}, 'prefix') as borgmatic_runtime_directory:
+ assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
+
+
+@pytest.mark.parametrize(
+ 'borgmatic_runtime_directory,expected_glob',
+ (
+ ('/foo/bar/baz/./borgmatic', 'foo/bar/baz/borgmatic'),
+ ('/foo/borgmatic/baz/./borgmatic', 'foo/borgmatic/baz/borgmatic'),
+ ('/foo/borgmatic-jti8idds/./borgmatic', 'foo/*/borgmatic'),
+ ),
+)
+def test_make_runtime_directory_glob(borgmatic_runtime_directory, expected_glob):
+ assert module.make_runtime_directory_glob(borgmatic_runtime_directory) == expected_glob
def test_get_borgmatic_state_directory_uses_config_option():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os.environ).should_receive('get').never()
assert (
module.get_borgmatic_state_directory(
@@ -89,16 +178,24 @@ def test_get_borgmatic_state_directory_uses_config_option():
)
-def test_get_borgmatic_state_directory_falls_back_to_environment_variable():
+def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
- flexmock(module.os.environ).should_receive('get').with_args(
- 'XDG_STATE_HOME', object
- ).and_return('/tmp')
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return('/tmp')
+
+ assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
+
+
+def test_get_borgmatic_state_directory_falls_back_to_state_directory():
+ flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
+ flexmock(module.os.environ).should_receive('get').with_args('XDG_STATE_HOME').and_return(None)
+ flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(
+ '/tmp'
+ )
assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
def test_get_borgmatic_state_directory_defaults_to_hard_coded_path():
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
- flexmock(module.os.environ).should_receive('get').and_return('/root/.local/state')
- assert module.get_borgmatic_state_directory({}) == '/root/.local/state/borgmatic'
+ flexmock(module.os.environ).should_receive('get').and_return(None)
+ assert module.get_borgmatic_state_directory({}) == '~/.local/state/borgmatic'
diff --git a/tests/unit/hooks/test_mariadb.py b/tests/unit/hooks/test_mariadb.py
index 8034afed..fb5612be 100644
--- a/tests/unit/hooks/test_mariadb.py
+++ b/tests/unit/hooks/test_mariadb.py
@@ -73,7 +73,12 @@ def test_dump_data_sources_dumps_each_database():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_dumps_with_password():
@@ -94,7 +99,9 @@ def test_dump_data_sources_dumps_with_password():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources([database], {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ [database], {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_dumps_all_databases_at_once():
@@ -112,7 +119,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
@@ -132,7 +141,12 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_database_names_to_dump_runs_mariadb_with_list_options():
@@ -434,7 +448,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
flexmock(module).should_receive('database_names_to_dump').and_return(())
with pytest.raises(ValueError):
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
@@ -445,7 +461,12 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ )
+ == []
+ )
def test_restore_data_source_dump_runs_mariadb_to_restore():
@@ -473,6 +494,7 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -501,6 +523,7 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -531,6 +554,7 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -568,6 +592,7 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -596,6 +621,7 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -645,6 +671,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
'username': 'cliusername',
'password': 'clipassword',
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -696,6 +723,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -717,4 +745,5 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
diff --git a/tests/unit/hooks/test_mongodb.py b/tests/unit/hooks/test_mongodb.py
index 86ac7b7e..b08dfaa4 100644
--- a/tests/unit/hooks/test_mongodb.py
+++ b/tests/unit/hooks/test_mongodb.py
@@ -41,7 +41,12 @@ def test_dump_data_sources_runs_mongodump_for_each_database():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_with_dry_run_skips_mongodump():
@@ -53,7 +58,12 @@ 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()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ )
+ == []
+ )
def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
@@ -82,7 +92,9 @@ def test_dump_data_sources_runs_mongodump_with_hostname_and_port():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_mongodump_with_username_and_password():
@@ -120,7 +132,9 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_mongodump_with_directory_format():
@@ -137,7 +151,12 @@ def test_dump_data_sources_runs_mongodump_with_directory_format():
shell=True,
).and_return(flexmock()).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == []
+ )
def test_dump_data_sources_runs_mongodump_with_options():
@@ -163,7 +182,9 @@ def test_dump_data_sources_runs_mongodump_with_options():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_mongodumpall_for_all_databases():
@@ -181,7 +202,9 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_build_dump_command_with_username_injection_attack_gets_escaped():
@@ -218,6 +241,7 @@ def test_restore_data_source_dump_runs_mongorestore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -257,6 +281,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -304,6 +329,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -359,6 +385,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
'username': 'cliusername',
'password': 'clipassword',
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -414,6 +441,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -443,6 +471,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -480,6 +509,7 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -509,6 +539,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -532,6 +563,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -560,4 +592,5 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
diff --git a/tests/unit/hooks/test_mysql.py b/tests/unit/hooks/test_mysql.py
index fa6145ac..496e6663 100644
--- a/tests/unit/hooks/test_mysql.py
+++ b/tests/unit/hooks/test_mysql.py
@@ -73,7 +73,12 @@ def test_dump_data_sources_dumps_each_database():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_dumps_with_password():
@@ -94,7 +99,9 @@ def test_dump_data_sources_dumps_with_password():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources([database], {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ [database], {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_dumps_all_databases_at_once():
@@ -112,7 +119,9 @@ def test_dump_data_sources_dumps_all_databases_at_once():
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_dumps_all_databases_separately_when_format_configured():
@@ -132,7 +141,12 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
dry_run_label=object,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_database_names_to_dump_runs_mysql_with_list_options():
@@ -432,7 +446,9 @@ def test_dump_data_sources_errors_for_missing_all_databases():
flexmock(module).should_receive('database_names_to_dump').and_return(())
with pytest.raises(ValueError):
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run():
@@ -443,7 +459,12 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
)
flexmock(module).should_receive('database_names_to_dump').and_return(())
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ )
+ == []
+ )
def test_restore_data_source_dump_runs_mysql_to_restore():
@@ -471,6 +492,7 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -499,6 +521,7 @@ def test_restore_data_source_dump_runs_mysql_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -527,6 +550,7 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -564,6 +588,7 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -592,6 +617,7 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -641,6 +667,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
'username': 'cliusername',
'password': 'clipassword',
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -692,6 +719,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -713,4 +741,5 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
diff --git a/tests/unit/hooks/test_postgresql.py b/tests/unit/hooks/test_postgresql.py
index 87f5dc0f..229a94e4 100644
--- a/tests/unit/hooks/test_postgresql.py
+++ b/tests/unit/hooks/test_postgresql.py
@@ -251,7 +251,12 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_raises_when_no_database_names_to_dump():
@@ -261,7 +266,9 @@ def test_dump_data_sources_raises_when_no_database_names_to_dump():
flexmock(module).should_receive('database_names_to_dump').and_return(())
with pytest.raises(ValueError):
- module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False)
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
@@ -270,7 +277,9 @@ def test_dump_data_sources_does_not_raise_when_no_database_names_to_dump():
flexmock(module).should_receive('make_dump_path').and_return('')
flexmock(module).should_receive('database_names_to_dump').and_return(())
- module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ ) == []
def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
@@ -287,7 +296,12 @@ def test_dump_data_sources_with_duplicate_dump_skips_pg_dump():
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == []
+ )
def test_dump_data_sources_with_dry_run_skips_pg_dump():
@@ -304,7 +318,12 @@ 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()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ )
+ == []
+ )
def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
@@ -340,7 +359,9 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_pg_dump_with_username_and_password():
@@ -376,7 +397,9 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_with_username_injection_attack_gets_escaped():
@@ -412,7 +435,9 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_pg_dump_with_directory_format():
@@ -443,7 +468,12 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
extra_environment={'PGSSLMODE': 'disable'},
).and_return(flexmock()).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == []
+ )
def test_dump_data_sources_runs_pg_dump_with_options():
@@ -476,7 +506,9 @@ def test_dump_data_sources_runs_pg_dump_with_options():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
@@ -498,7 +530,9 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_dump_data_sources_runs_non_default_pg_dump():
@@ -532,7 +566,9 @@ def test_dump_data_sources_runs_non_default_pg_dump():
run_to_completion=False,
).and_return(process).once()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == [process]
+ assert module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ ) == [process]
def test_restore_data_source_dump_runs_pg_restore():
@@ -584,6 +620,7 @@ def test_restore_data_source_dump_runs_pg_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -646,6 +683,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -706,6 +744,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -785,6 +824,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
'username': 'cliusername',
'password': 'clipassword',
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -864,6 +904,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -925,6 +966,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -964,6 +1006,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1008,6 +1051,7 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1077,6 +1121,7 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1101,6 +1146,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1153,6 +1199,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -1209,4 +1256,5 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
'username': None,
'password': None,
},
+ borgmatic_runtime_directory='/run/borgmatic',
)
diff --git a/tests/unit/hooks/test_sqlite.py b/tests/unit/hooks/test_sqlite.py
index ea560e30..460a08d9 100644
--- a/tests/unit/hooks/test_sqlite.py
+++ b/tests/unit/hooks/test_sqlite.py
@@ -18,15 +18,20 @@ def test_use_streaming_false_for_no_databases():
def test_dump_data_sources_logs_and_skips_if_dump_already_exists():
databases = [{'path': '/path/to/database', 'name': 'database'}]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic/database'
)
flexmock(module.os.path).should_receive('exists').and_return(True)
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == []
+ )
def test_dump_data_sources_dumps_each_database():
@@ -36,9 +41,9 @@ def test_dump_data_sources_dumps_each_database():
]
processes = [flexmock(), flexmock()]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic/database'
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
@@ -46,7 +51,12 @@ def test_dump_data_sources_dumps_each_database():
processes[1]
)
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_with_path_injection_attack_gets_escaped():
@@ -55,9 +65,9 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
]
processes = [flexmock()]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic/database'
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
@@ -67,13 +77,18 @@ def test_dump_data_sources_with_path_injection_attack_gets_escaped():
"'/path/to/database1; naughty-command'",
'.dump',
'>',
- '/path/to/dump/database',
+ '/run/borgmatic/database',
),
shell=True,
run_to_completion=False,
).and_return(processes[0])
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
@@ -82,16 +97,21 @@ def test_dump_data_sources_with_non_existent_path_warns_and_dumps_database():
]
processes = [flexmock()]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.logger).should_receive('warning').once()
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic'
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
flexmock(module).should_receive('execute_command').and_return(processes[0])
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
@@ -100,32 +120,42 @@ def test_dump_data_sources_with_name_all_warns_and_dumps_all_databases():
]
processes = [flexmock()]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.logger).should_receive(
'warning'
).twice() # once for the name=all, once for the non-existent path
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic/database'
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
flexmock(module).should_receive('execute_command').and_return(processes[0])
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=False) == processes
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=False
+ )
+ == processes
+ )
def test_dump_data_sources_does_not_dump_if_dry_run():
databases = [{'path': '/path/to/database', 'name': 'database'}]
- flexmock(module).should_receive('make_dump_path').and_return('/path/to/dump')
+ flexmock(module).should_receive('make_dump_path').and_return('/run/borgmatic')
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
- '/path/to/dump/database'
+ '/run/borgmatic'
)
flexmock(module.os.path).should_receive('exists').and_return(False)
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
flexmock(module).should_receive('execute_command').never()
- assert module.dump_data_sources(databases, {}, 'test.yaml', dry_run=True) == []
+ assert (
+ module.dump_data_sources(
+ databases, {}, 'test.yaml', borgmatic_runtime_directory='/run/borgmatic', dry_run=True
+ )
+ == []
+ )
def test_restore_data_source_dump_restores_database():
@@ -152,6 +182,7 @@ def test_restore_data_source_dump_restores_database():
dry_run=False,
extract_process=extract_process,
connection_params={'restore_path': None},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -181,6 +212,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
dry_run=False,
extract_process=extract_process,
connection_params={'restore_path': 'cli/path/to/database'},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -210,6 +242,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
dry_run=False,
extract_process=extract_process,
connection_params={'restore_path': None},
+ borgmatic_runtime_directory='/run/borgmatic',
)
@@ -228,4 +261,5 @@ def test_restore_data_source_dump_does_not_restore_database_if_dry_run():
dry_run=True,
extract_process=extract_process,
connection_params={'restore_path': None},
+ borgmatic_runtime_directory='/run/borgmatic',
)