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).

This commit is contained in:
Dan Helfman 2024-11-15 18:15:32 -08:00
parent c0721a8cad
commit 295bfb0c57
25 changed files with 864 additions and 519 deletions

4
NEWS
View File

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

View File

@ -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
@ -546,11 +552,12 @@ 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,
@ -600,6 +607,7 @@ 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')
@ -730,14 +738,16 @@ 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) 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(

View File

@ -38,37 +38,41 @@ 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}
) as borgmatic_runtime_directory:
for base_directory in (
'borgmatic',
borgmatic_runtime_directory,
borgmatic_source_directory,
):
borgmatic_manifest_path = 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)

View File

@ -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'
)
@ -72,53 +72,63 @@ def run_create(
**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(
with borgmatic.config.paths.Runtime_directory(config) 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'),

View File

@ -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'],
@ -181,17 +182,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
@ -330,6 +331,7 @@ def run_restore(
global_arguments,
local_path,
remote_path,
borgmatic_runtime_directory,
):
'''
Run the "restore" action for the given repository, but only if the repository matches the
@ -346,105 +348,110 @@ def run_restore(
f'{repository.get("label", repository["path"])}: 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) 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,
)
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)

View File

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

View File

@ -275,8 +275,8 @@ def run_actions(
'''
Given parsed command-line arguments as an argparse.ArgumentParser instance, the configuration
filename, a configuration dict, a sequence of loaded configuration paths, local and remote paths
to Borg, a local Borg version string, and a repository name, run all actions from the
command-line arguments on the given repository.
to Borg, a local Borg version string, a repository name, and the borgmatic runtime directory,
run all actions from the command-line arguments on the given repository.
Yield JSON output strings from executing any actions that produce JSON.

View File

@ -1,4 +1,8 @@
import logging
import os
import tempfile
logger = logging.getLogger(__name__)
def expand_user_in_path(path):
@ -26,28 +30,70 @@ def get_borgmatic_source_directory(config):
return expand_user_in_path(config.get('borgmatic_source_directory') or '~/.borgmatic')
def get_borgmatic_runtime_directory(config):
class Runtime_directory:
'''
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.
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.
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.
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.
'''
return expand_user_in_path(
os.path.join(
def __init__(self, config):
'''
Given a configuration dict, 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:
self.temporary_directory = tempfile.TemporaryDirectory(
prefix='borgmatic', dir=os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp'
)
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'
)
)
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 get_borgmatic_state_directory(config):
@ -59,10 +105,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',
)
)

View File

@ -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,30 +181,36 @@ 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='*'
),
)

View File

@ -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,11 @@ 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 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 +40,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,30 +90,36 @@ 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='*'
),
)

View File

@ -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,30 +180,36 @@ 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='*'
),
)

View File

@ -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,30 +206,36 @@ 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='*'
),
)

View File

@ -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,30 +76,36 @@ 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='*'
),
)

View File

@ -9,6 +9,8 @@ Documentation=https://torsion.org/borgmatic/
[Service]
Type=oneshot
RuntimeDirectory=borgmatic
StateDirectory=borgmatic
# Load single encrypted credential.
LoadCredentialEncrypted=borgmatic.pw

View File

@ -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,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@ -47,13 +47,58 @@ 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.os.path).should_receive('join').with_args(
'borgmatic', 'bootstrap', 'manifest.json'
).and_return(flexmock()).once()
flexmock(module.os.path).should_receive('join').with_args(
borgmatic_runtime_directory, 'bootstrap', 'manifest.json'
).and_return(flexmock()).once()
flexmock(module.os.path).should_receive('join').with_args(
'/source', 'bootstrap', 'manifest.json'
).and_return(flexmock()).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 +114,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["/borgmatic/config.yaml"]}',
@ -96,9 +144,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 +158,10 @@ 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.os.path).should_receive('join').and_return(flexmock())
extract_process = flexmock(stdout=flexmock(read=lambda: ''))
flexmock(module.borgmatic.borg.extract).should_receive('extract_archive').and_return(
extract_process
@ -128,9 +177,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 +191,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(read=lambda: '{"config_paths": ["/oops'),
)
@ -162,9 +211,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 +225,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(read=lambda: '{}'),
)
@ -210,6 +259,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',
@ -244,6 +296,9 @@ 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()
)
extract_process = flexmock(
stdout=flexmock(
read=lambda: '{"config_paths": ["borgmatic/config.yaml"]}',

View File

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

View File

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

View File

@ -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'
@ -123,16 +120,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'
)
@ -166,16 +161,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'
)
@ -210,16 +203,14 @@ 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'
)
@ -253,6 +244,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 +252,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 +271,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 +284,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 +304,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 +317,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 +335,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 +347,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 +367,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 +465,10 @@ 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.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 +489,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 +502,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')
@ -534,6 +524,7 @@ def test_run_restore_restores_each_data_source():
global_arguments=flexmock(dry_run=False),
local_path=flexmock(),
remote_path=flexmock(),
borgmatic_runtime_directory='/run/borgmatic',
)
@ -541,6 +532,9 @@ 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.hooks.dispatch).should_receive(
'call_hooks_even_if_unconfigured'
).never()
@ -554,6 +548,7 @@ def test_run_restore_bails_for_non_matching_repository():
global_arguments=flexmock(dry_run=False),
local_path=flexmock(),
remote_path=flexmock(),
borgmatic_runtime_directory='/run/borgmatic',
)
@ -563,6 +558,10 @@ 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.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 +598,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 +611,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')
@ -632,6 +633,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
global_arguments=flexmock(dry_run=False),
local_path=flexmock(),
remote_path=flexmock(),
borgmatic_runtime_directory='/run/borgmatic',
)
@ -641,6 +643,10 @@ 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.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 +683,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 +696,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')
@ -710,6 +718,7 @@ def test_run_restore_skips_missing_data_source():
global_arguments=flexmock(dry_run=False),
local_path=flexmock(),
remote_path=flexmock(),
borgmatic_runtime_directory='/run/borgmatic',
)
@ -720,6 +729,10 @@ 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.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 +762,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 +775,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')
@ -782,4 +797,5 @@ def test_run_restore_restores_data_sources_from_different_hooks():
global_arguments=flexmock(dry_run=False),
local_path=flexmock(),
remote_path=flexmock(),
borgmatic_runtime_directory='/run/borgmatic',
)

View File

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

View File

@ -33,53 +33,120 @@ 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)
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) 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)
config = {'user_runtime_directory': '/run/borgmatic', 'borgmatic_source_directory': '/nope'}
with module.Runtime_directory(config) 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'
)
assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
with module.Runtime_directory({}) 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'
)
with module.Runtime_directory({}) 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'
)
assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
with module.Runtime_directory({}) 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'
)
with module.Runtime_directory({}) 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)
with module.Runtime_directory({}) 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)
assert module.get_borgmatic_runtime_directory({}) == '/tmp/./borgmatic'
with module.Runtime_directory({}) 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)
assert module.get_borgmatic_runtime_directory({}) == '/run/user/0/./borgmatic'
with module.Runtime_directory({}) as borgmatic_runtime_directory:
assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
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 +156,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'

View File

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

View File

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

View File

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

View File

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

View File

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