mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-08-02 22:53:01 +02:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ac2c86d2d | ||
|
|
b7ecc9c6c3 | ||
|
|
4f728b1cd4 | ||
|
|
78db924b70 | ||
|
|
b55834c1ca |
@@ -1,6 +1,9 @@
|
|||||||
2.1.6.dev0
|
2.1.6.dev0
|
||||||
* #1256: Fix a race condition in which borgmatic sometimes swallows Borg error output without
|
* #1267: Support multiple borgmatic instances run in parallel—as long as they're operating on
|
||||||
logging it.
|
different Borg repositories. This required an internal change to the runtime directory path,
|
||||||
|
which may cause a one-time performance hit (Borg cache misses) for Borg 1.x users of database and
|
||||||
|
filesystem hooks. See the documentation for more information:
|
||||||
|
https://torsion.org/borgmatic/how-to/make-per-application-backups/#parallelism
|
||||||
* #1300: Fix the "source_directories_must_exist" option to support source directories relative to a
|
* #1300: Fix the "source_directories_must_exist" option to support source directories relative to a
|
||||||
"working_directory".
|
"working_directory".
|
||||||
* #1301: Expand the "patterns_from" and "exclude_from" options to support paths containing tildes
|
* #1301: Expand the "patterns_from" and "exclude_from" options to support paths containing tildes
|
||||||
@@ -10,11 +13,6 @@
|
|||||||
* #1303: For the MariaDB hook, include only a subset of system data when dumping the "mysql" system
|
* #1303: For the MariaDB hook, include only a subset of system data when dumping the "mysql" system
|
||||||
database (or "all" databases), so the dump is actually restorable. See the documentation for more
|
database (or "all" databases), so the dump is actually restorable. See the documentation for more
|
||||||
information: https://torsion.org/borgmatic/reference/configuration/data-sources/mariadb/
|
information: https://torsion.org/borgmatic/reference/configuration/data-sources/mariadb/
|
||||||
* #1308: Update the Apprise monitoring hook's "url" option to support loading credentials with the
|
|
||||||
"{credential ...}" syntax. See the documentation for more information:
|
|
||||||
https://torsion.org/borgmatic/reference/configuration/credentials/
|
|
||||||
* #1309: Add a "--quick-stats" flag and corresponding "quick_statistics" option for showing only
|
|
||||||
abbreviated statistics for the "create" action. Borg 1.4.5+ only.
|
|
||||||
* Update the KeePassXC credential hook to support KeePassXC's secret service integration. See the
|
* Update the KeePassXC credential hook to support KeePassXC's secret service integration. See the
|
||||||
documentation for more information:
|
documentation for more information:
|
||||||
https://torsion.org/borgmatic/reference/configuration/credentials/keepassxc/
|
https://torsion.org/borgmatic/reference/configuration/credentials/keepassxc/
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ attrs==26.1.0 # via jsonschema, referencing, -r binary_requirements.
|
|||||||
certifi==2026.4.22 # via apprise, requests, -r binary_requirements.in
|
certifi==2026.4.22 # via apprise, requests, -r binary_requirements.in
|
||||||
charset-normalizer==3.4.7 # via requests, -r binary_requirements.in
|
charset-normalizer==3.4.7 # via requests, -r binary_requirements.in
|
||||||
click==8.3.3 # via apprise, -r binary_requirements.in
|
click==8.3.3 # via apprise, -r binary_requirements.in
|
||||||
idna==3.14 # via requests, -r binary_requirements.in
|
idna==3.13 # via requests, -r binary_requirements.in
|
||||||
jsonschema==4.26.0 # via borgmatic, -r binary_requirements.in
|
jsonschema==4.26.0 # via borgmatic, -r binary_requirements.in
|
||||||
jsonschema-specifications==2025.9.1 # via jsonschema, -r binary_requirements.in
|
jsonschema-specifications==2025.9.1 # via jsonschema, -r binary_requirements.in
|
||||||
markdown==3.10.2 # via apprise, -r binary_requirements.in
|
markdown==3.10.2 # via apprise, -r binary_requirements.in
|
||||||
@@ -14,8 +14,8 @@ oauthlib==3.3.1 # via requests-oauthlib, -r binary_requirements.in
|
|||||||
packaging==26.2 # via borgmatic, -r binary_requirements.in
|
packaging==26.2 # via borgmatic, -r binary_requirements.in
|
||||||
pyyaml==6.0.3 # via apprise, -r binary_requirements.in
|
pyyaml==6.0.3 # via apprise, -r binary_requirements.in
|
||||||
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r binary_requirements.in
|
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r binary_requirements.in
|
||||||
requests==2.34.0 # via apprise, borgmatic, requests-oauthlib, -r binary_requirements.in
|
requests==2.33.1 # via apprise, borgmatic, requests-oauthlib, -r binary_requirements.in
|
||||||
requests-oauthlib==2.0.0 # via apprise, -r binary_requirements.in
|
requests-oauthlib==2.0.0 # via apprise, -r binary_requirements.in
|
||||||
rpds-py==0.30.0 # via jsonschema, referencing, -r binary_requirements.in
|
rpds-py==0.30.0 # via jsonschema, referencing, -r binary_requirements.in
|
||||||
ruamel-yaml==0.19.1 # via borgmatic, -r binary_requirements.in
|
ruamel-yaml==0.19.1 # via borgmatic, -r binary_requirements.in
|
||||||
urllib3==2.7.0 # via requests, -r binary_requirements.in
|
urllib3==2.6.3 # via requests, -r binary_requirements.in
|
||||||
|
|||||||
@@ -788,7 +788,7 @@ def run_check(
|
|||||||
'''
|
'''
|
||||||
logger.info('Running consistency checks')
|
logger.info('Running consistency checks')
|
||||||
|
|
||||||
repository_id = borgmatic.borg.check.get_repository_id(
|
repository_id = borgmatic.borg.repo_info.get_repository_id(
|
||||||
repository['path'],
|
repository['path'],
|
||||||
config,
|
config,
|
||||||
local_borg_version,
|
local_borg_version,
|
||||||
@@ -844,7 +844,9 @@ def run_check(
|
|||||||
|
|
||||||
if 'spot' in checks:
|
if 'spot' in checks:
|
||||||
logger.info('Running spot check')
|
logger.info('Running spot check')
|
||||||
with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
|
with borgmatic.config.paths.Runtime_directory(
|
||||||
|
config, repository_id
|
||||||
|
) as borgmatic_runtime_directory:
|
||||||
spot_check(
|
spot_check(
|
||||||
repository,
|
repository,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import argparse
|
|
||||||
import datetime
|
|
||||||
import itertools
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import borgmatic.borg.extract
|
import borgmatic.borg.extract
|
||||||
import borgmatic.borg.info
|
import borgmatic.borg.repo_info
|
||||||
import borgmatic.borg.repo_list
|
import borgmatic.borg.repo_list
|
||||||
import borgmatic.config.paths
|
import borgmatic.config.paths
|
||||||
|
|
||||||
@@ -104,39 +101,6 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version):
|
|||||||
Raise CalledProcessError or OSError if Borg could not be run.
|
Raise CalledProcessError or OSError if Borg could not be run.
|
||||||
'''
|
'''
|
||||||
config = make_bootstrap_config(bootstrap_arguments)
|
config = make_bootstrap_config(bootstrap_arguments)
|
||||||
|
|
||||||
if bootstrap_arguments.archive == 'latest':
|
|
||||||
archives_data = json.loads(
|
|
||||||
borgmatic.borg.info.display_archives_info(
|
|
||||||
bootstrap_arguments.repository,
|
|
||||||
config,
|
|
||||||
local_borg_version,
|
|
||||||
info_arguments=argparse.Namespace(archive=None, prefix=None, json=True),
|
|
||||||
global_arguments=global_arguments,
|
|
||||||
local_path=bootstrap_arguments.local_path,
|
|
||||||
remote_path=bootstrap_arguments.remote_path,
|
|
||||||
)
|
|
||||||
)['archives']
|
|
||||||
|
|
||||||
get_repo_archive_format = lambda archive_data: archive_data['command_line'][-1]
|
|
||||||
get_archive_start = lambda archive_data: datetime.datetime.fromisoformat(
|
|
||||||
archive_data['start']
|
|
||||||
)
|
|
||||||
|
|
||||||
latest_archives = {
|
|
||||||
repo_archive_format: max(archives_data, key=get_archive_start)['name']
|
|
||||||
for repo_archive_format, archives_data in itertools.groupby(
|
|
||||||
sorted(archives_data, key=get_repo_archive_format),
|
|
||||||
key=get_repo_archive_format,
|
|
||||||
)
|
|
||||||
if not repo_archive_format.endswith('checkpoint')
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(latest_archives) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
f'The repository appears to have multiple "latest" archives, each with a different archive name format: {", ".join(sorted(latest_archives.values()))}. Please select one with --archive.'
|
|
||||||
)
|
|
||||||
|
|
||||||
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
|
archive_name = borgmatic.borg.repo_list.resolve_archive_name(
|
||||||
bootstrap_arguments.repository,
|
bootstrap_arguments.repository,
|
||||||
bootstrap_arguments.archive,
|
bootstrap_arguments.archive,
|
||||||
@@ -146,8 +110,18 @@ def run_bootstrap(bootstrap_arguments, global_arguments, local_borg_version):
|
|||||||
local_path=bootstrap_arguments.local_path,
|
local_path=bootstrap_arguments.local_path,
|
||||||
remote_path=bootstrap_arguments.remote_path,
|
remote_path=bootstrap_arguments.remote_path,
|
||||||
)
|
)
|
||||||
|
repository_id = borgmatic.borg.repo_info.get_repository_id(
|
||||||
|
bootstrap_arguments.repository,
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
global_arguments,
|
||||||
|
local_path=bootstrap_arguments.local_path,
|
||||||
|
remote_path=bootstrap_arguments.remote_path,
|
||||||
|
)
|
||||||
|
|
||||||
with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
|
with borgmatic.config.paths.Runtime_directory(
|
||||||
|
config, repository_id
|
||||||
|
) as borgmatic_runtime_directory:
|
||||||
manifest_config_paths = load_config_paths_from_archive(
|
manifest_config_paths = load_config_paths_from_archive(
|
||||||
bootstrap_arguments.repository,
|
bootstrap_arguments.repository,
|
||||||
archive_name,
|
archive_name,
|
||||||
|
|||||||
@@ -42,8 +42,18 @@ def run_create(
|
|||||||
|
|
||||||
logger.info(f'Creating archive{dry_run_label}')
|
logger.info(f'Creating archive{dry_run_label}')
|
||||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||||
|
repository_id = borgmatic.borg.repo_info.get_repository_id(
|
||||||
|
repository['path'],
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
global_arguments,
|
||||||
|
local_path=local_path,
|
||||||
|
remote_path=remote_path,
|
||||||
|
)
|
||||||
|
|
||||||
with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
|
with borgmatic.config.paths.Runtime_directory(
|
||||||
|
config, repository_id
|
||||||
|
) as borgmatic_runtime_directory:
|
||||||
patterns = pattern.process_patterns(
|
patterns = pattern.process_patterns(
|
||||||
pattern.collect_patterns(config, working_directory),
|
pattern.collect_patterns(config, working_directory),
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -536,8 +536,18 @@ def run_restore(
|
|||||||
'''
|
'''
|
||||||
logger.info(f'Restoring data sources from archive {restore_arguments.archive}')
|
logger.info(f'Restoring data sources from archive {restore_arguments.archive}')
|
||||||
working_directory = borgmatic.config.paths.get_working_directory(config)
|
working_directory = borgmatic.config.paths.get_working_directory(config)
|
||||||
|
repository_id = borgmatic.borg.repo_info.get_repository_id(
|
||||||
|
repository['path'],
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
global_arguments,
|
||||||
|
local_path=local_path,
|
||||||
|
remote_path=remote_path,
|
||||||
|
)
|
||||||
|
|
||||||
with borgmatic.config.paths.Runtime_directory(config) as borgmatic_runtime_directory:
|
with borgmatic.config.paths.Runtime_directory(
|
||||||
|
config, repository_id
|
||||||
|
) as borgmatic_runtime_directory:
|
||||||
patterns = borgmatic.actions.pattern.process_patterns(
|
patterns = borgmatic.actions.pattern.process_patterns(
|
||||||
borgmatic.actions.pattern.collect_patterns(config, working_directory),
|
borgmatic.actions.pattern.collect_patterns(config, working_directory),
|
||||||
config,
|
config,
|
||||||
|
|||||||
+1
-33
@@ -1,10 +1,8 @@
|
|||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
import borgmatic.config.paths
|
import borgmatic.config.paths
|
||||||
from borgmatic.borg import environment, feature, flags, repo_info
|
from borgmatic.borg import environment, feature, flags
|
||||||
from borgmatic.execute import DO_NOT_CAPTURE, execute_command
|
from borgmatic.execute import DO_NOT_CAPTURE, execute_command
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -82,36 +80,6 @@ def make_check_name_flags(checks, archive_filter_flags):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_repository_id(
|
|
||||||
repository_path,
|
|
||||||
config,
|
|
||||||
local_borg_version,
|
|
||||||
global_arguments,
|
|
||||||
local_path,
|
|
||||||
remote_path,
|
|
||||||
):
|
|
||||||
'''
|
|
||||||
Given a local or remote repository path, a configuration dict, the local Borg version, global
|
|
||||||
arguments, and local/remote commands to run, return the corresponding Borg repository ID.
|
|
||||||
|
|
||||||
Raise ValueError if the Borg repository ID cannot be determined.
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
return json.loads(
|
|
||||||
repo_info.display_repository_info(
|
|
||||||
repository_path,
|
|
||||||
config,
|
|
||||||
local_borg_version,
|
|
||||||
argparse.Namespace(json=True),
|
|
||||||
global_arguments,
|
|
||||||
local_path,
|
|
||||||
remote_path,
|
|
||||||
),
|
|
||||||
)['repository']['id']
|
|
||||||
except (json.JSONDecodeError, KeyError):
|
|
||||||
raise ValueError(f'Cannot determine Borg repository ID for {repository_path}')
|
|
||||||
|
|
||||||
|
|
||||||
def check_archives(
|
def check_archives(
|
||||||
repository_path,
|
repository_path,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -374,9 +374,7 @@ def create_archive(
|
|||||||
|
|
||||||
if json:
|
if json:
|
||||||
output_log_level = None
|
output_log_level = None
|
||||||
elif config.get('list_details') or (
|
elif config.get('list_details') or (config.get('statistics') and not dry_run):
|
||||||
(config.get('statistics') or config.get('quick_statistics')) and not dry_run
|
|
||||||
):
|
|
||||||
output_log_level = logging.ANSWER
|
output_log_level = logging.ANSWER
|
||||||
else:
|
else:
|
||||||
output_log_level = logging.INFO
|
output_log_level = logging.INFO
|
||||||
@@ -388,11 +386,6 @@ def create_archive(
|
|||||||
create_flags += (
|
create_flags += (
|
||||||
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
|
(('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
|
||||||
+ (('--stats',) if config.get('statistics') and not json and not dry_run else ())
|
+ (('--stats',) if config.get('statistics') and not json and not dry_run else ())
|
||||||
+ (
|
|
||||||
('--quick-stats',)
|
|
||||||
if config.get('quick_statistics') and not json and not dry_run
|
|
||||||
else ()
|
|
||||||
)
|
|
||||||
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
|
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
|
||||||
+ (('--progress',) if config.get('progress') else ())
|
+ (('--progress',) if config.get('progress') else ())
|
||||||
+ (('--json',) if json else ())
|
+ (('--json',) if json else ())
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import argparse
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
@@ -81,3 +83,33 @@ def display_repository_info(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_repository_id(
|
||||||
|
repository_path,
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
global_arguments,
|
||||||
|
local_path,
|
||||||
|
remote_path,
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Given a local or remote repository path, a configuration dict, the local Borg version, global
|
||||||
|
arguments, and local/remote commands to run, return the corresponding Borg repository ID.
|
||||||
|
|
||||||
|
Raise ValueError if the Borg repository ID cannot be determined.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
return json.loads(
|
||||||
|
display_repository_info(
|
||||||
|
repository_path,
|
||||||
|
config,
|
||||||
|
local_borg_version,
|
||||||
|
argparse.Namespace(json=True),
|
||||||
|
global_arguments,
|
||||||
|
local_path,
|
||||||
|
remote_path,
|
||||||
|
),
|
||||||
|
)['repository']['id']
|
||||||
|
except (json.JSONDecodeError, KeyError):
|
||||||
|
raise ValueError(f'Cannot determine Borg repository ID for {repository_path}')
|
||||||
|
|||||||
@@ -896,13 +896,6 @@ def make_parsers(schema, unparsed_arguments): # noqa: PLR0915
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Display statistics of archive',
|
help='Display statistics of archive',
|
||||||
)
|
)
|
||||||
create_group.add_argument(
|
|
||||||
'--quick-stats',
|
|
||||||
dest='quick_statistics',
|
|
||||||
default=None,
|
|
||||||
action='store_true',
|
|
||||||
help='Display statistics of archive, skipping repository-wide "All archives" and chunk index statistics [Borg 1.4.5+ only]',
|
|
||||||
)
|
|
||||||
create_group.add_argument(
|
create_group.add_argument(
|
||||||
'--list',
|
'--list',
|
||||||
'--files',
|
'--files',
|
||||||
|
|||||||
+39
-20
@@ -1,6 +1,7 @@
|
|||||||
import contextlib
|
import contextlib
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
@@ -84,6 +85,25 @@ def replace_temporary_subdirectory_with_glob(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Fixed_name_temporary_directory:
|
||||||
|
'''
|
||||||
|
A class whose instances can stand-in for tempfile.TemporaryDirectory's, except the temporary
|
||||||
|
filename path is fixed rather than randomly generated.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
'''
|
||||||
|
Given a temporary directory path, save it off for later.
|
||||||
|
'''
|
||||||
|
self.name = path
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
'''
|
||||||
|
Remove the temporary directory path.
|
||||||
|
'''
|
||||||
|
shutil.rmtree(self.name, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
class Runtime_directory:
|
class Runtime_directory:
|
||||||
'''
|
'''
|
||||||
A Python context manager for creating and cleaning up the borgmatic runtime directory used for
|
A Python context manager for creating and cleaning up the borgmatic runtime directory used for
|
||||||
@@ -98,16 +118,17 @@ class Runtime_directory:
|
|||||||
automatically gets cleaned up as necessary.
|
automatically gets cleaned up as necessary.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config, repository_id):
|
||||||
'''
|
'''
|
||||||
Given a configuration dict determine the borgmatic runtime directory, creating a secure,
|
Given a configuration dict and the Borg ID for a repository, determine the borgmatic runtime
|
||||||
temporary directory within it if necessary. Defaults to $XDG_RUNTIME_DIR/./borgmatic or
|
directory, creating a secure, temporary directory within it if necessary. Defaults to
|
||||||
$RUNTIME_DIRECTORY/./borgmatic or $TMPDIR/borgmatic-[random]/./borgmatic or
|
$XDG_RUNTIME_DIR/borgmatic-[repository_id]/./borgmatic or
|
||||||
$TEMP/borgmatic-[random]/./borgmatic or /tmp/borgmatic-[random]/./borgmatic where "[random]"
|
$RUNTIME_DIRECTORY/borgmatic-[repository_id]/./borgmatic or
|
||||||
is a randomly generated string intended to avoid path collisions.
|
$TMPDIR/borgmatic-[random]/./borgmatic or $TEMP/borgmatic-[random]/./borgmatic or
|
||||||
|
/tmp/borgmatic-[random]/./borgmatic where "[random]" is a randomly generated string and
|
||||||
If XDG_RUNTIME_DIR or RUNTIME_DIRECTORY is set and already ends in "/borgmatic", then don't
|
"[repository_id]" is the Borg repository ID being operated on. Both strings are intended to
|
||||||
tack on a second "/borgmatic" path component.
|
avoid path collisions, and the random string helps avoid predictable temporary path attacks
|
||||||
|
in shared temporary directories.
|
||||||
|
|
||||||
The "/./" is taking advantage of a Borg feature such that the part of the path before the "/./"
|
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
|
does not get stored in the file path within an archive. That way, the path of the runtime
|
||||||
@@ -125,7 +146,8 @@ class Runtime_directory:
|
|||||||
if not runtime_directory.startswith(os.path.sep):
|
if not runtime_directory.startswith(os.path.sep):
|
||||||
raise ValueError('The runtime directory must be an absolute path')
|
raise ValueError('The runtime directory must be an absolute path')
|
||||||
|
|
||||||
self.temporary_directory = None
|
runtime_directory = os.path.join(runtime_directory, f'borgmatic-{repository_id}')
|
||||||
|
self.temporary_directory = Fixed_name_temporary_directory(runtime_directory)
|
||||||
else:
|
else:
|
||||||
base_directory = (
|
base_directory = (
|
||||||
os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' # noqa: S108
|
os.environ.get('TMPDIR') or os.environ.get('TEMP') or '/tmp' # noqa: S108
|
||||||
@@ -141,11 +163,9 @@ class Runtime_directory:
|
|||||||
)
|
)
|
||||||
runtime_directory = self.temporary_directory.name
|
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(
|
self.runtime_path = expand_user_in_path(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
base_path if final_directory == 'borgmatic' else runtime_directory,
|
runtime_directory,
|
||||||
'.', # Borg 1.4+ "slashdot" hack.
|
'.', # Borg 1.4+ "slashdot" hack.
|
||||||
'borgmatic',
|
'borgmatic',
|
||||||
),
|
),
|
||||||
@@ -162,14 +182,13 @@ class Runtime_directory:
|
|||||||
|
|
||||||
def __exit__(self, exception_type, exception, traceback):
|
def __exit__(self, exception_type, exception, traceback):
|
||||||
'''
|
'''
|
||||||
Delete any temporary directory that was created as part of initialization.
|
Delete the temporary directory that was created as part of initialization.
|
||||||
'''
|
'''
|
||||||
if self.temporary_directory:
|
# The cleanup() call errors if, for instance, there's still a
|
||||||
# The cleanup() call errors if, for instance, there's still a
|
# mounted filesystem within the temporary directory. There's
|
||||||
# mounted filesystem within the temporary directory. There's
|
# nothing we can do about that here, so swallow the error.
|
||||||
# nothing we can do about that here, so swallow the error.
|
with contextlib.suppress(OSError):
|
||||||
with contextlib.suppress(OSError):
|
self.temporary_directory.cleanup()
|
||||||
self.temporary_directory.cleanup()
|
|
||||||
|
|
||||||
|
|
||||||
def make_runtime_directory_glob(borgmatic_runtime_directory):
|
def make_runtime_directory_glob(borgmatic_runtime_directory):
|
||||||
|
|||||||
@@ -1091,15 +1091,6 @@ properties:
|
|||||||
Corresponds to the "--stats" flag on those actions. Defaults to
|
Corresponds to the "--stats" flag on those actions. Defaults to
|
||||||
false.
|
false.
|
||||||
example: true
|
example: true
|
||||||
quick_statistics:
|
|
||||||
type: boolean
|
|
||||||
description: |
|
|
||||||
Display statistics for an archive when running supported actions,
|
|
||||||
skipping the repository-wide "All archives" and chunk index
|
|
||||||
statistics to save some time. Corresponds to the "--quick-stats"
|
|
||||||
flag on those actions. Defaults to false. (This option is supported
|
|
||||||
for Borg 1.4.5+ only.)
|
|
||||||
example: true
|
|
||||||
list_details:
|
list_details:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
@@ -2834,9 +2825,7 @@ properties:
|
|||||||
properties:
|
properties:
|
||||||
url:
|
url:
|
||||||
type: string
|
type: string
|
||||||
description: |
|
description: URL of this Apprise service.
|
||||||
URL of this Apprise service. Supports the
|
|
||||||
"{credential ...}" syntax.
|
|
||||||
example: "gotify://hostname/token"
|
example: "gotify://hostname/token"
|
||||||
label:
|
label:
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
+7
-31
@@ -372,21 +372,13 @@ def log_buffer_lines(
|
|||||||
yield log_record.getMessage()
|
yield log_record.getMessage()
|
||||||
|
|
||||||
|
|
||||||
def raise_for_process_errors(
|
def raise_for_process_errors(buffer_readers, process_metadatas, borg_local_path, borg_exit_codes):
|
||||||
buffer_readers,
|
|
||||||
process_metadatas,
|
|
||||||
output_log_level,
|
|
||||||
borg_local_path,
|
|
||||||
borg_exit_codes,
|
|
||||||
capture_stderr=False,
|
|
||||||
):
|
|
||||||
'''
|
'''
|
||||||
Given a dict from buffer object to Buffer_reader, a dict from subprocess.Popen() instance to
|
Given a dict from buffer object to Buffer_reader, a dict from subprocess.Popen() instance to
|
||||||
Process_metadata instance, a requested output log level for stdout, Borg's local path, a
|
Process_metadata instance, Borg's local path, a sequence of exit code configuration dicts, check
|
||||||
sequence of exit code configuration dicts, and whether to capture stderr, check the given
|
the given processes for error or warning exit codes. If found, vent or kill any running
|
||||||
processes for error or warning exit codes. If found, vent or kill any running processes and
|
processes. In the case of an error exit code, raise. In the case of warning, return
|
||||||
drain any remaining buffer lines. In the case of an error exit code, raise. In the case of
|
Exit_status.WARNING. Otherwise, return None.
|
||||||
warning, return Exit_status.WARNING. Otherwise, return None.
|
|
||||||
'''
|
'''
|
||||||
result_status = None
|
result_status = None
|
||||||
|
|
||||||
@@ -412,13 +404,6 @@ def raise_for_process_errors(
|
|||||||
result_status = Exit_status.WARNING
|
result_status = Exit_status.WARNING
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Drain and log remaining buffer lines, so no output gets lost. But swallow captured output,
|
|
||||||
# since we're raising below instead of yielding captured lines.
|
|
||||||
tuple(
|
|
||||||
log_remaining_buffer_lines(
|
|
||||||
buffer_readers, process_metadatas, output_log_level, borg_local_path, capture_stderr
|
|
||||||
)
|
|
||||||
)
|
|
||||||
last_lines = process_metadatas[process].last_lines
|
last_lines = process_metadatas[process].last_lines
|
||||||
|
|
||||||
# If an error occurs, include its output in the raised exception so that we don't
|
# If an error occurs, include its output in the raised exception so that we don't
|
||||||
@@ -444,9 +429,6 @@ def log_remaining_buffer_lines(
|
|||||||
whether to capture stderr, drain and log any remaining output lines from the buffers until
|
whether to capture stderr, drain and log any remaining output lines from the buffers until
|
||||||
they're empty. Additionally, for any log records with a log level the same as the output log
|
they're empty. Additionally, for any log records with a log level the same as the output log
|
||||||
level, yield those log messages for capture.
|
level, yield those log messages for capture.
|
||||||
|
|
||||||
The main difference between this function and log_buffer_lines() is that this one doesn't just
|
|
||||||
do one "turn of the crank" of logging buffer output; it completely drains any remaining output.
|
|
||||||
'''
|
'''
|
||||||
for output_buffer, reader in buffer_readers.items():
|
for output_buffer, reader in buffer_readers.items():
|
||||||
if not reader.process:
|
if not reader.process:
|
||||||
@@ -464,7 +446,6 @@ def log_remaining_buffer_lines(
|
|||||||
borg_local_path=borg_local_path,
|
borg_local_path=borg_local_path,
|
||||||
command=reader.process.args,
|
command=reader.process.args,
|
||||||
),
|
),
|
||||||
last_lines=process_metadatas[reader.process].last_lines,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -513,7 +494,7 @@ def log_outputs(
|
|||||||
for buffer in output_buffers_for_process(process, exclude_stdouts)
|
for buffer in output_buffers_for_process(process, exclude_stdouts)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Log output lines for each process until they all exit or one errors.
|
# Log output lines for each process until they all exit.
|
||||||
while True:
|
while True:
|
||||||
yield from log_buffer_lines(
|
yield from log_buffer_lines(
|
||||||
buffer_readers, process_metadatas, output_log_level, borg_local_path, capture_stderr
|
buffer_readers, process_metadatas, output_log_level, borg_local_path, capture_stderr
|
||||||
@@ -521,12 +502,7 @@ def log_outputs(
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
raise_for_process_errors(
|
raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers, process_metadatas, borg_local_path, borg_exit_codes
|
||||||
process_metadatas,
|
|
||||||
output_log_level,
|
|
||||||
borg_local_path,
|
|
||||||
borg_exit_codes,
|
|
||||||
capture_stderr,
|
|
||||||
)
|
)
|
||||||
== Exit_status.WARNING
|
== Exit_status.WARNING
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
import borgmatic.hooks.credential.parse
|
|
||||||
import borgmatic.hooks.monitoring.logs
|
import borgmatic.hooks.monitoring.logs
|
||||||
import borgmatic.hooks.monitoring.monitor
|
import borgmatic.hooks.monitoring.monitor
|
||||||
|
|
||||||
@@ -77,12 +76,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
|||||||
logger.info(f'Pinging Apprise services: {labels_string}{dry_run_string}')
|
logger.info(f'Pinging Apprise services: {labels_string}{dry_run_string}')
|
||||||
|
|
||||||
apprise_object = apprise.Apprise()
|
apprise_object = apprise.Apprise()
|
||||||
apprise_object.add(
|
apprise_object.add(list(map(operator.itemgetter('url'), hook_config.get('services'))))
|
||||||
[
|
|
||||||
borgmatic.hooks.credential.parse.resolve_credential(service['url'], config)
|
|
||||||
for service in hook_config.get('services')
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -58,19 +58,25 @@ choice](https://torsion.org/borgmatic/how-to/set-up-backups/#autopilot), each
|
|||||||
entry using borgmatic's `--config` flag instead of relying on
|
entry using borgmatic's `--config` flag instead of relying on
|
||||||
`/etc/borgmatic.d`.
|
`/etc/borgmatic.d`.
|
||||||
|
|
||||||
|
<a id="limitations"></a>
|
||||||
|
|
||||||
## Limitations
|
## Parallelism
|
||||||
|
|
||||||
borgmatic does not currently support its own parallelism—being run multiple
|
<span class="minilink minilink-addedin">New in version 2.1.6</span> borgmatic
|
||||||
times on the same machine simultaneously. In particular, many of the [data
|
supports multiple borgmatic instances run on the same machine in parallel—as
|
||||||
source
|
long as they're operating on different Borg repositories.
|
||||||
hooks](https://torsion.org/borgmatic/reference/configuration/data-sources/) rely
|
|
||||||
on global borgmatic runtime files which can't be shared across processes, and
|
|
||||||
therefore multiple borgmatic instances on the same machine would interfere with
|
|
||||||
each other.
|
|
||||||
|
|
||||||
A single borgmatic instance also doesn't currently support running multiple Borg
|
However, note that a single borgmatic instance doesn't currently support running
|
||||||
instances in parallel on the same machine.
|
multiple Borg instances in parallel on the same machine.
|
||||||
|
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">Prior to version 2.1.6</span> borgmatic
|
||||||
|
did not support parallel borgmatic runs on the same machine simultaneously. In
|
||||||
|
particular, many of the [data source
|
||||||
|
hooks](https://torsion.org/borgmatic/reference/configuration/data-sources/)
|
||||||
|
relied on global borgmatic runtime files which couldn't be shared across
|
||||||
|
processes, and therefore multiple borgmatic instances on the same machine
|
||||||
|
interfered with each other.
|
||||||
|
|
||||||
|
|
||||||
<a id="archive-naming"></a>
|
<a id="archive-naming"></a>
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ eleventyNavigation:
|
|||||||
parent: 🚨 Monitoring
|
parent: 🚨 Monitoring
|
||||||
---
|
---
|
||||||
<span class="minilink minilink-addedin">New in version 1.8.4</span>
|
<span class="minilink minilink-addedin">New in version 1.8.4</span>
|
||||||
[Apprise](https://appriseit.com/) is a local notification library
|
[Apprise](https://github.com/caronc/apprise/wiki) is a local notification library
|
||||||
that "allows you to send a notification to almost all of the most popular
|
that "allows you to send a notification to almost all of the most popular
|
||||||
[notification services](https://appriseit.com/services/) available to
|
[notification services](https://github.com/caronc/apprise/wiki) available to
|
||||||
us today such as: Telegram, Discord, Slack, Amazon SNS, Gotify, etc."
|
us today such as: Telegram, Discord, Slack, Amazon SNS, Gotify, etc."
|
||||||
|
|
||||||
Depending on how you installed borgmatic, it may not have come with Apprise.
|
Depending on how you installed borgmatic, it may not have come with Apprise.
|
||||||
@@ -22,7 +22,7 @@ sudo uv tool install borgmatic[Apprise]
|
|||||||
Omit `sudo` if borgmatic is installed as a non-root user.
|
Omit `sudo` if borgmatic is installed as a non-root user.
|
||||||
|
|
||||||
Once Apprise is installed, configure borgmatic to notify one or more [Apprise
|
Once Apprise is installed, configure borgmatic to notify one or more [Apprise
|
||||||
services](https://appriseit.com/services/). For example:
|
services](https://github.com/caronc/apprise/wiki). For example:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
apprise:
|
apprise:
|
||||||
|
|||||||
@@ -29,11 +29,18 @@ probes the following values:
|
|||||||
You can see the runtime directory path that borgmatic selects by running with
|
You can see the runtime directory path that borgmatic selects by running with
|
||||||
`--verbosity 2` and looking for `Using runtime directory` in the output.
|
`--verbosity 2` and looking for `Using runtime directory` in the output.
|
||||||
|
|
||||||
Regardless of the runtime directory selected, borgmatic stores its files
|
Regardless of the runtime directory selected, borgmatic stores its files within
|
||||||
within a `borgmatic` subdirectory of the runtime directory. Additionally, in
|
a `borgmatic` subdirectory of the runtime directory. Additionally, in the case
|
||||||
the case of `TMPDIR`, `TEMP`, and the hard-coded `/tmp`, borgmatic creates a
|
of `TMPDIR`, `TEMP`, and the hard-coded `/tmp`, borgmatic creates a randomly
|
||||||
randomly named subdirectory in an effort to reduce path collisions in shared
|
named subdirectory in an effort to reduce path collisions and predictable
|
||||||
system temporary directories.
|
temporary path attacks in shared temporary directories.
|
||||||
|
|
||||||
|
<span class="minilink minilink-addedin">New in version 2.1.6</span> When
|
||||||
|
constructing the runtime directory, borgmatic now creates a subdirectory named
|
||||||
|
after the Borg ID of the current repository. This means that borgmatic supports
|
||||||
|
multiple borgmatic instances run [in
|
||||||
|
parallel](https://torsion.org/borgmatic/how-to/make-per-application-backups/#parallelism)—as
|
||||||
|
long as they're operating on different Borg repositories.
|
||||||
|
|
||||||
<span class="minilink minilink-addedin">Prior to version 1.9.0</span>
|
<span class="minilink minilink-addedin">Prior to version 1.9.0</span>
|
||||||
borgmatic created temporary streaming database dumps within the `~/.borgmatic`
|
borgmatic created temporary streaming database dumps within the `~/.borgmatic`
|
||||||
|
|||||||
+1
-2
@@ -5,14 +5,13 @@ authors = [
|
|||||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||||
]
|
]
|
||||||
description = "Simple, configuration-driven backup software for servers and workstations"
|
description = "Simple, configuration-driven backup software for servers and workstations"
|
||||||
license = "GPL-3.0-or-later"
|
|
||||||
license-files = ["LICENSE"]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Environment :: Console",
|
"Environment :: Console",
|
||||||
"Intended Audience :: System Administrators",
|
"Intended Audience :: System Administrators",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
"Programming Language :: Python",
|
"Programming Language :: Python",
|
||||||
"Topic :: Security :: Cryptography",
|
"Topic :: Security :: Cryptography",
|
||||||
"Topic :: System :: Archiving :: Backup",
|
"Topic :: System :: Archiving :: Backup",
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ certifi==2026.4.22 # via apprise, requests, -r test_requirements.in
|
|||||||
charset-normalizer==3.4.7 # via requests, -r test_requirements.in
|
charset-normalizer==3.4.7 # via requests, -r test_requirements.in
|
||||||
click>=8.1.8
|
click>=8.1.8
|
||||||
codespell==2.4.2 # via -r test_requirements.in
|
codespell==2.4.2 # via -r test_requirements.in
|
||||||
coverage==7.14.0 # via pytest-cov, -r test_requirements.in
|
coverage==7.13.5 # via pytest-cov, -r test_requirements.in
|
||||||
flexmock==0.13.0 # via -r test_requirements.in
|
flexmock==0.13.0 # via -r test_requirements.in
|
||||||
idna==3.14 # via requests, -r test_requirements.in
|
idna==3.13 # via requests, -r test_requirements.in
|
||||||
iniconfig==2.3.0 # via pytest, -r test_requirements.in
|
iniconfig==2.3.0 # via pytest, -r test_requirements.in
|
||||||
jsonschema==4.26.0 # via -r test_requirements.in
|
jsonschema==4.26.0 # via -r test_requirements.in
|
||||||
jsonschema-specifications==2025.9.1 # via jsonschema, -r test_requirements.in
|
jsonschema-specifications==2025.9.1 # via jsonschema, -r test_requirements.in
|
||||||
@@ -22,9 +22,9 @@ pytest-cov==7.1.0 # via -r test_requirements.in
|
|||||||
pytest-timeout==2.4.0 # via -r test_requirements.in
|
pytest-timeout==2.4.0 # via -r test_requirements.in
|
||||||
pyyaml>5.0.0
|
pyyaml>5.0.0
|
||||||
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r test_requirements.in
|
referencing==0.37.0 # via jsonschema, jsonschema-specifications, -r test_requirements.in
|
||||||
requests==2.34.0 # via apprise, requests-oauthlib, -r test_requirements.in
|
requests==2.33.1 # via apprise, requests-oauthlib, -r test_requirements.in
|
||||||
requests-oauthlib==2.0.0 # via apprise, -r test_requirements.in
|
requests-oauthlib==2.0.0 # via apprise, -r test_requirements.in
|
||||||
rpds-py==0.30.0 # via jsonschema, referencing, -r test_requirements.in
|
rpds-py==0.30.0 # via jsonschema, referencing, -r test_requirements.in
|
||||||
ruamel-yaml>0.15.0
|
ruamel-yaml>0.15.0
|
||||||
typing-extensions==4.15.0 # via -r test_requirements.in
|
typing-extensions==4.15.0 # via -r test_requirements.in
|
||||||
urllib3==2.7.0 # via requests, -r test_requirements.in
|
urllib3==2.6.3 # via requests, -r test_requirements.in
|
||||||
|
|||||||
@@ -557,43 +557,6 @@ def test_log_outputs_with_unfinished_process_re_polls():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_log_outputs_includes_error_output_when_output_spans_multiple_chunks():
|
|
||||||
flexmock(module.logger).should_receive('log')
|
|
||||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
|
||||||
flexmock(module).should_receive('command_for_process').and_return('python')
|
|
||||||
|
|
||||||
process = subprocess.Popen(
|
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
'-c',
|
|
||||||
(
|
|
||||||
'import os, sys; '
|
|
||||||
f'os.write(sys.stdout.fileno(), b"x" * {module.READ_CHUNK_SIZE + 10}); '
|
|
||||||
'os.write(sys.stdout.fileno(), b"\\nERROR: critical failure"); '
|
|
||||||
'os.close(1); '
|
|
||||||
'os._exit(2)'
|
|
||||||
),
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
)
|
|
||||||
flexmock(module).should_receive('output_buffers_for_process').and_return((process.stdout,))
|
|
||||||
|
|
||||||
with pytest.raises(subprocess.CalledProcessError) as error:
|
|
||||||
tuple(
|
|
||||||
module.log_outputs(
|
|
||||||
(process,),
|
|
||||||
exclude_stdouts=(),
|
|
||||||
output_log_level=logging.INFO,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert error.value.output
|
|
||||||
assert 'ERROR: critical failure' in error.value.output
|
|
||||||
|
|
||||||
|
|
||||||
def test_read_lines_uses_system_locale_when_decoding_output():
|
def test_read_lines_uses_system_locale_when_decoding_output():
|
||||||
flexmock(module.locale).should_receive('getpreferredencoding').and_return('ISO-8859-1')
|
flexmock(module.locale).should_receive('getpreferredencoding').and_return('ISO-8859-1')
|
||||||
process = subprocess.Popen(['echo', b'\xc4pple'], stdout=subprocess.PIPE)
|
process = subprocess.Popen(['echo', b'\xc4pple'], stdout=subprocess.PIPE)
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ def test_run_bootstrap_does_not_raise():
|
|||||||
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
flexmock(module.borgmatic.borg.repo_list).should_receive('resolve_archive_name').and_return(
|
||||||
'archive',
|
'archive',
|
||||||
)
|
)
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('repo')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
@@ -222,6 +223,7 @@ def test_run_bootstrap_translates_ssh_command_argument_to_config():
|
|||||||
local_path='borg7',
|
local_path='borg7',
|
||||||
remote_path='borg8',
|
remote_path='borg8',
|
||||||
).and_return('archive')
|
).and_return('archive')
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('repo')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1838,7 +1838,7 @@ def test_spot_check_without_any_source_paths_errors():
|
|||||||
|
|
||||||
def test_run_check_checks_archives_for_configured_repository():
|
def test_run_check_checks_archives_for_configured_repository():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module).should_receive('upgrade_check_times')
|
flexmock(module).should_receive('upgrade_check_times')
|
||||||
flexmock(module).should_receive('parse_checks')
|
flexmock(module).should_receive('parse_checks')
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
||||||
@@ -1873,7 +1873,7 @@ def test_run_check_checks_archives_for_configured_repository():
|
|||||||
|
|
||||||
def test_run_check_runs_configured_extract_check():
|
def test_run_check_runs_configured_extract_check():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module).should_receive('upgrade_check_times')
|
flexmock(module).should_receive('upgrade_check_times')
|
||||||
flexmock(module).should_receive('parse_checks')
|
flexmock(module).should_receive('parse_checks')
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
||||||
@@ -1906,7 +1906,7 @@ def test_run_check_runs_configured_extract_check():
|
|||||||
|
|
||||||
def test_run_check_runs_configured_spot_check():
|
def test_run_check_runs_configured_spot_check():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module).should_receive('upgrade_check_times')
|
flexmock(module).should_receive('upgrade_check_times')
|
||||||
flexmock(module).should_receive('parse_checks')
|
flexmock(module).should_receive('parse_checks')
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
||||||
@@ -1942,7 +1942,7 @@ def test_run_check_runs_configured_spot_check():
|
|||||||
|
|
||||||
def test_run_check_without_checks_runs_nothing_except_hooks():
|
def test_run_check_without_checks_runs_nothing_except_hooks():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('get_repository_id').and_return(flexmock())
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module).should_receive('upgrade_check_times')
|
flexmock(module).should_receive('upgrade_check_times')
|
||||||
flexmock(module).should_receive('parse_checks')
|
flexmock(module).should_receive('parse_checks')
|
||||||
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
flexmock(module.borgmatic.borg.check).should_receive('make_archive_filter_flags').and_return(())
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from borgmatic.actions import create as module
|
|||||||
|
|
||||||
def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
@@ -49,6 +50,7 @@ def test_run_create_executes_and_calls_hooks_for_configured_repository():
|
|||||||
|
|
||||||
def test_run_create_with_both_list_and_json_errors():
|
def test_run_create_with_both_list_and_json_errors():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').never()
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
||||||
create_arguments = flexmock(
|
create_arguments = flexmock(
|
||||||
@@ -80,6 +82,7 @@ def test_run_create_with_both_list_and_json_errors():
|
|||||||
|
|
||||||
def test_run_create_with_both_list_and_progress_errors():
|
def test_run_create_with_both_list_and_progress_errors():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').never()
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
|
||||||
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
|
||||||
create_arguments = flexmock(
|
create_arguments = flexmock(
|
||||||
@@ -111,6 +114,7 @@ def test_run_create_with_both_list_and_progress_errors():
|
|||||||
|
|
||||||
def test_run_create_produces_json():
|
def test_run_create_produces_json():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
@@ -156,6 +160,7 @@ def test_run_create_with_active_dumps_roundtrips_via_checkpoint_archive():
|
|||||||
mock_dump_process.should_receive('poll').and_return(None).and_return(0)
|
mock_dump_process.should_receive('poll').and_return(None).and_return(0)
|
||||||
|
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
@@ -234,6 +239,7 @@ def test_run_create_with_active_dumps_json_updates_archive_info():
|
|||||||
}
|
}
|
||||||
|
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
@@ -316,6 +322,7 @@ def mock_dump_cleanup(config, borgmatic_runtime_directory, patterns, dry_run):
|
|||||||
|
|
||||||
def test_run_create_with_active_dumps_removes_data_source_dumps_with_original_patterns():
|
def test_run_create_with_active_dumps_removes_data_source_dumps_with_original_patterns():
|
||||||
flexmock(module.logger).answer = lambda message: None
|
flexmock(module.logger).answer = lambda message: None
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
flexmock(),
|
flexmock(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1175,6 +1175,7 @@ def test_run_restore_restores_each_data_source():
|
|||||||
}
|
}
|
||||||
|
|
||||||
borgmatic_runtime_directory = flexmock()
|
borgmatic_runtime_directory = flexmock()
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
)
|
)
|
||||||
@@ -1249,6 +1250,7 @@ def test_run_restore_restores_data_source_by_falling_back_to_all_name():
|
|||||||
}
|
}
|
||||||
|
|
||||||
borgmatic_runtime_directory = flexmock()
|
borgmatic_runtime_directory = flexmock()
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
)
|
)
|
||||||
@@ -1311,6 +1313,7 @@ def test_run_restore_restores_data_source_configured_with_all_name():
|
|||||||
}
|
}
|
||||||
|
|
||||||
borgmatic_runtime_directory = flexmock()
|
borgmatic_runtime_directory = flexmock()
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
)
|
)
|
||||||
@@ -1395,6 +1398,7 @@ def test_run_restore_skips_missing_data_source():
|
|||||||
}
|
}
|
||||||
|
|
||||||
borgmatic_runtime_directory = flexmock()
|
borgmatic_runtime_directory = flexmock()
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
)
|
)
|
||||||
@@ -1479,6 +1483,7 @@ def test_run_restore_restores_data_sources_from_different_hooks():
|
|||||||
}
|
}
|
||||||
|
|
||||||
borgmatic_runtime_directory = flexmock()
|
borgmatic_runtime_directory = flexmock()
|
||||||
|
flexmock(module.borgmatic.borg.repo_info).should_receive('get_repository_id').and_return('id')
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
|
||||||
borgmatic_runtime_directory,
|
borgmatic_runtime_directory,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -272,54 +272,6 @@ def test_make_check_name_flags_with_extract_omits_extract_flag():
|
|||||||
assert flags == ()
|
assert flags == ()
|
||||||
|
|
||||||
|
|
||||||
def test_get_repository_id_with_valid_json_does_not_raise():
|
|
||||||
config = {}
|
|
||||||
flexmock(module.repo_info).should_receive('display_repository_info').and_return(
|
|
||||||
'{"repository": {"id": "repo"}}',
|
|
||||||
)
|
|
||||||
|
|
||||||
assert module.get_repository_id(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path='borg',
|
|
||||||
remote_path=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_repository_id_with_json_error_raises():
|
|
||||||
config = {}
|
|
||||||
flexmock(module.repo_info).should_receive('display_repository_info').and_return(
|
|
||||||
'{"unexpected": {"id": "repo"}}',
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.get_repository_id(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path='borg',
|
|
||||||
remote_path=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_repository_id_with_missing_json_keys_raises():
|
|
||||||
config = {}
|
|
||||||
flexmock(module.repo_info).should_receive('display_repository_info').and_return('{invalid JSON')
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
module.get_repository_id(
|
|
||||||
repository_path='repo',
|
|
||||||
config=config,
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
local_path='borg',
|
|
||||||
remote_path=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_check_archives_with_progress_passes_through_to_borg():
|
def test_check_archives_with_progress_passes_through_to_borg():
|
||||||
config = {'progress': True}
|
config = {'progress': True}
|
||||||
flexmock(module).should_receive('make_check_name_flags').with_args(
|
flexmock(module).should_receive('make_check_name_flags').with_args(
|
||||||
|
|||||||
@@ -1477,44 +1477,6 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats():
|
|||||||
'source_directories': ['foo', 'bar'],
|
'source_directories': ['foo', 'bar'],
|
||||||
'repositories': ['repo'],
|
'repositories': ['repo'],
|
||||||
'exclude_patterns': None,
|
'exclude_patterns': None,
|
||||||
'statistics': True,
|
|
||||||
},
|
|
||||||
patterns=[Pattern('foo'), Pattern('bar')],
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
borgmatic_runtime_directory='/borgmatic/run',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_archive_with_quick_stats_and_dry_run_calls_borg_without_quick_stats():
|
|
||||||
# --dry-run and --quick-stats are mutually exclusive, see:
|
|
||||||
# https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
|
|
||||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
|
||||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
|
||||||
(('borg', 'create', '--dry-run'), REPO_ARCHIVE, flexmock()),
|
|
||||||
)
|
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
|
||||||
flexmock(module).should_receive('execute_command').with_args(
|
|
||||||
('borg', 'create', '--dry-run', '--info', *REPO_ARCHIVE),
|
|
||||||
output_log_level=logging.INFO,
|
|
||||||
output_file=None,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
working_directory=None,
|
|
||||||
environment=None,
|
|
||||||
)
|
|
||||||
insert_logging_mock(logging.INFO)
|
|
||||||
|
|
||||||
module.create_archive(
|
|
||||||
dry_run=True,
|
|
||||||
repository_path='repo',
|
|
||||||
config={
|
|
||||||
'source_directories': ['foo', 'bar'],
|
|
||||||
'repositories': ['repo'],
|
|
||||||
'exclude_patterns': None,
|
|
||||||
'quick_statistics': True,
|
|
||||||
},
|
},
|
||||||
patterns=[Pattern('foo'), Pattern('bar')],
|
patterns=[Pattern('foo'), Pattern('bar')],
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
@@ -1844,42 +1806,6 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_flag():
|
|||||||
'source_directories': ['foo*'],
|
'source_directories': ['foo*'],
|
||||||
'repositories': ['repo'],
|
'repositories': ['repo'],
|
||||||
'exclude_patterns': None,
|
'exclude_patterns': None,
|
||||||
'statistics': True,
|
|
||||||
},
|
|
||||||
patterns=[Pattern('foo'), Pattern('bar')],
|
|
||||||
local_borg_version='1.2.3',
|
|
||||||
global_arguments=flexmock(),
|
|
||||||
borgmatic_runtime_directory='/borgmatic/run',
|
|
||||||
json=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert json_output == '[]'
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_archive_with_quick_stats_and_json_calls_borg_without_quick_stats_flag():
|
|
||||||
flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
|
|
||||||
flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
|
|
||||||
flexmock(module).should_receive('make_base_create_command').and_return(
|
|
||||||
(('borg', 'create'), REPO_ARCHIVE, flexmock()),
|
|
||||||
)
|
|
||||||
flexmock(module.environment).should_receive('make_environment')
|
|
||||||
flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
|
|
||||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
|
||||||
('borg', 'create', '--json', *REPO_ARCHIVE),
|
|
||||||
working_directory=None,
|
|
||||||
environment=None,
|
|
||||||
borg_local_path='borg',
|
|
||||||
borg_exit_codes=None,
|
|
||||||
).and_yield('[]')
|
|
||||||
|
|
||||||
json_output = module.create_archive(
|
|
||||||
dry_run=False,
|
|
||||||
repository_path='repo',
|
|
||||||
config={
|
|
||||||
'source_directories': ['foo*'],
|
|
||||||
'repositories': ['repo'],
|
|
||||||
'exclude_patterns': None,
|
|
||||||
'quick_statistics': True,
|
|
||||||
},
|
},
|
||||||
patterns=[Pattern('foo'), Pattern('bar')],
|
patterns=[Pattern('foo'), Pattern('bar')],
|
||||||
local_borg_version='1.2.3',
|
local_borg_version='1.2.3',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
from flexmock import flexmock
|
from flexmock import flexmock
|
||||||
|
|
||||||
from borgmatic.borg import repo_info as module
|
from borgmatic.borg import repo_info as module
|
||||||
@@ -612,3 +613,51 @@ def test_display_repository_info_calls_borg_with_working_directory():
|
|||||||
repo_info_arguments=flexmock(json=False),
|
repo_info_arguments=flexmock(json=False),
|
||||||
global_arguments=flexmock(),
|
global_arguments=flexmock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_repository_id_with_valid_json_does_not_raise():
|
||||||
|
config = {}
|
||||||
|
flexmock(module).should_receive('display_repository_info').and_return(
|
||||||
|
'{"repository": {"id": "repo"}}',
|
||||||
|
)
|
||||||
|
|
||||||
|
assert module.get_repository_id(
|
||||||
|
repository_path='repo',
|
||||||
|
config=config,
|
||||||
|
local_borg_version='1.2.3',
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path='borg',
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_repository_id_with_json_error_raises():
|
||||||
|
config = {}
|
||||||
|
flexmock(module).should_receive('display_repository_info').and_return(
|
||||||
|
'{"unexpected": {"id": "repo"}}',
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.get_repository_id(
|
||||||
|
repository_path='repo',
|
||||||
|
config=config,
|
||||||
|
local_borg_version='1.2.3',
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path='borg',
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_repository_id_with_missing_json_keys_raises():
|
||||||
|
config = {}
|
||||||
|
flexmock(module).should_receive('display_repository_info').and_return('{invalid JSON')
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
module.get_repository_id(
|
||||||
|
repository_path='repo',
|
||||||
|
config=config,
|
||||||
|
local_borg_version='1.2.3',
|
||||||
|
global_arguments=flexmock(),
|
||||||
|
local_path='borg',
|
||||||
|
remote_path=None,
|
||||||
|
)
|
||||||
|
|||||||
+81
-107
@@ -62,65 +62,58 @@ def test_replace_temporary_subdirectory_with_glob_uses_custom_temporary_director
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_fixed_name_temporary_directory_cleanup_does_not_raise():
|
||||||
|
flexmock(module.shutil).should_receive('rmtree')
|
||||||
|
|
||||||
|
module.Fixed_name_temporary_directory('/path').cleanup()
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_uses_config_option():
|
def test_runtime_directory_uses_config_option():
|
||||||
|
flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(
|
||||||
|
flexmock(cleanup=lambda: None)
|
||||||
|
)
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
config = {'user_runtime_directory': '/run', 'borgmatic_source_directory': '/nope'}
|
config = {'user_runtime_directory': '/run', 'borgmatic_source_directory': '/nope'}
|
||||||
|
|
||||||
with module.Runtime_directory(config) as borgmatic_runtime_directory:
|
with module.Runtime_directory(config, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_uses_config_option_without_adding_duplicate_borgmatic_subdirectory():
|
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
|
||||||
flexmock(module.os).should_receive('makedirs')
|
|
||||||
config = {'user_runtime_directory': '/run/borgmatic', 'borgmatic_source_directory': '/nope'}
|
|
||||||
|
|
||||||
with module.Runtime_directory(config) as borgmatic_runtime_directory:
|
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_relative_config_option_errors():
|
def test_runtime_directory_with_relative_config_option_errors():
|
||||||
flexmock(module.os).should_receive('makedirs').never()
|
flexmock(module.os).should_receive('makedirs').never()
|
||||||
config = {'user_runtime_directory': 'run', 'borgmatic_source_directory': '/nope'}
|
config = {'user_runtime_directory': 'run', 'borgmatic_source_directory': '/nope'}
|
||||||
|
|
||||||
with pytest.raises(ValueError), module.Runtime_directory(config):
|
with pytest.raises(ValueError), module.Runtime_directory(config, repository_id='id'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_xdg_runtime_dir():
|
def test_runtime_directory_falls_back_to_xdg_runtime_dir(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
|
flexmock(cleanup=lambda: None)
|
||||||
'/run',
|
|
||||||
)
|
)
|
||||||
|
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
||||||
|
monkeypatch.setenv('XDG_RUNTIME_DIR', '/run')
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_xdg_runtime_dir_without_adding_duplicate_borgmatic_subdirectory():
|
def test_runtime_directory_with_relative_xdg_runtime_dir_errors(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
monkeypatch.setenv('XDG_RUNTIME_DIR', 'run')
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(
|
|
||||||
'/run/borgmatic',
|
|
||||||
)
|
|
||||||
flexmock(module.os).should_receive('makedirs')
|
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_relative_xdg_runtime_dir_errors():
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return('run')
|
|
||||||
flexmock(module.os).should_receive('makedirs').never()
|
flexmock(module.os).should_receive('makedirs').never()
|
||||||
|
|
||||||
with pytest.raises(ValueError), module.Runtime_directory({}):
|
with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_runtime_directory():
|
def test_runtime_directory_falls_back_to_runtime_directory(monkeypatch):
|
||||||
|
flexmock(module).should_receive('Fixed_name_temporary_directory').and_return(
|
||||||
|
flexmock(cleanup=lambda: None)
|
||||||
|
)
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
@@ -128,26 +121,12 @@ def test_runtime_directory_falls_back_to_runtime_directory():
|
|||||||
)
|
)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
assert borgmatic_runtime_directory == '/run/borgmatic-id/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_runtime_directory_without_adding_duplicate_borgmatic_subdirectory():
|
def test_runtime_directory_with_relative_runtime_directory_errors(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
|
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
|
||||||
).and_return(
|
|
||||||
'/run/borgmatic',
|
|
||||||
)
|
|
||||||
flexmock(module.os).should_receive('makedirs')
|
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
|
||||||
assert borgmatic_runtime_directory == '/run/./borgmatic'
|
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_relative_runtime_directory_errors():
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
|
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
@@ -155,19 +134,21 @@ def test_runtime_directory_with_relative_runtime_directory_errors():
|
|||||||
)
|
)
|
||||||
flexmock(module.os).should_receive('makedirs').never()
|
flexmock(module.os).should_receive('makedirs').never()
|
||||||
|
|
||||||
with pytest.raises(ValueError), module.Runtime_directory({}):
|
with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up():
|
def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_that_get_cleaned_up(
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('/run')
|
monkeypatch.setenv('TMPDIR', '/run')
|
||||||
temporary_directory = flexmock(name='/run/borgmatic-1234')
|
temporary_directory = flexmock(name='/run/borgmatic-1234')
|
||||||
temporary_directory.should_receive('cleanup').once()
|
temporary_directory.should_receive('cleanup').once()
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
||||||
@@ -176,35 +157,37 @@ def test_runtime_directory_falls_back_to_tmpdir_and_adds_temporary_subdirectory_
|
|||||||
).and_return(temporary_directory)
|
).and_return(temporary_directory)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
|
assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_relative_tmpdir_errors():
|
def test_runtime_directory_with_relative_tmpdir_errors(monkeypatch):
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return('run')
|
monkeypatch.setenv('TMPDIR', 'run')
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').never()
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').never()
|
||||||
flexmock(module.os).should_receive('makedirs').never()
|
flexmock(module.os).should_receive('makedirs').never()
|
||||||
|
|
||||||
with pytest.raises(ValueError), module.Runtime_directory({}):
|
with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up():
|
def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_that_get_cleaned_up(
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
|
monkeypatch.delenv('TMPDIR', raising=False)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('/run')
|
monkeypatch.setenv('TEMP', '/run')
|
||||||
temporary_directory = flexmock(name='/run/borgmatic-1234')
|
temporary_directory = flexmock(name='/run/borgmatic-1234')
|
||||||
temporary_directory.should_receive('cleanup').once()
|
temporary_directory.should_receive('cleanup').once()
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
||||||
@@ -213,36 +196,38 @@ def test_runtime_directory_falls_back_to_temp_and_adds_temporary_subdirectory_th
|
|||||||
).and_return(temporary_directory)
|
).and_return(temporary_directory)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
|
assert borgmatic_runtime_directory == '/run/borgmatic-1234/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_relative_temp_errors():
|
def test_runtime_directory_with_relative_temp_errors(monkeypatch):
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('XDG_RUNTIME_DIR').and_return(None)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
|
monkeypatch.delenv('TMPDIR', raising=False)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return('run')
|
monkeypatch.setenv('TEMP', 'run')
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').never()
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').never()
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with pytest.raises(ValueError), module.Runtime_directory({}):
|
with pytest.raises(ValueError), module.Runtime_directory({}, repository_id='id'):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up():
|
def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_subdirectory_that_get_cleaned_up(
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
|
monkeypatch.delenv('TMPDIR', raising=False)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None)
|
monkeypatch.delenv('TEMP', raising=False)
|
||||||
temporary_directory = flexmock(name='/tmp/borgmatic-1234')
|
temporary_directory = flexmock(name='/tmp/borgmatic-1234')
|
||||||
temporary_directory.should_receive('cleanup').once()
|
temporary_directory.should_receive('cleanup').once()
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
||||||
@@ -251,20 +236,20 @@ def test_runtime_directory_falls_back_to_hard_coded_tmp_path_and_adds_temporary_
|
|||||||
).and_return(temporary_directory)
|
).and_return(temporary_directory)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
|
assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_runtime_directory_with_erroring_cleanup_does_not_raise():
|
def test_runtime_directory_with_erroring_cleanup_does_not_raise(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_RUNTIME_DIR', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.RUNTIME_DIRECTORY
|
module.Systemd_directories.RUNTIME_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TMPDIR').and_return(None)
|
monkeypatch.delenv('TMPDIR', raising=False)
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('TEMP').and_return(None)
|
monkeypatch.delenv('TEMP', raising=False)
|
||||||
temporary_directory = flexmock(name='/tmp/borgmatic-1234')
|
temporary_directory = flexmock(name='/tmp/borgmatic-1234')
|
||||||
temporary_directory.should_receive('cleanup').and_raise(OSError).once()
|
temporary_directory.should_receive('cleanup').and_raise(OSError).once()
|
||||||
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
flexmock(module.tempfile).should_receive('TemporaryDirectory').with_args(
|
||||||
@@ -273,7 +258,7 @@ def test_runtime_directory_with_erroring_cleanup_does_not_raise():
|
|||||||
).and_return(temporary_directory)
|
).and_return(temporary_directory)
|
||||||
flexmock(module.os).should_receive('makedirs')
|
flexmock(module.os).should_receive('makedirs')
|
||||||
|
|
||||||
with module.Runtime_directory({}) as borgmatic_runtime_directory:
|
with module.Runtime_directory({}, repository_id='id') as borgmatic_runtime_directory:
|
||||||
assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
|
assert borgmatic_runtime_directory == '/tmp/borgmatic-1234/./borgmatic'
|
||||||
|
|
||||||
|
|
||||||
@@ -291,7 +276,6 @@ def test_make_runtime_directory_glob(borgmatic_runtime_directory, expected_glob)
|
|||||||
|
|
||||||
def test_get_borgmatic_state_directory_uses_config_option():
|
def test_get_borgmatic_state_directory_uses_config_option():
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
||||||
flexmock(module.os.environ).should_receive('get').never()
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
module.get_borgmatic_state_directory(
|
module.get_borgmatic_state_directory(
|
||||||
@@ -301,16 +285,16 @@ def test_get_borgmatic_state_directory_uses_config_option():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home():
|
def test_get_borgmatic_state_directory_falls_back_to_xdg_state_home(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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('/tmp')
|
monkeypatch.setenv('XDG_STATE_HOME', '/tmp')
|
||||||
|
|
||||||
assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
|
assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_get_borgmatic_state_directory_falls_back_to_state_directory():
|
def test_get_borgmatic_state_directory_falls_back_to_state_directory(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
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)
|
monkeypatch.delenv('XDG_STATE_HOME', raising=False)
|
||||||
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
flexmock(module).should_receive('resolve_systemd_directory').with_args(
|
||||||
module.Systemd_directories.STATE_DIRECTORY
|
module.Systemd_directories.STATE_DIRECTORY
|
||||||
).and_return(
|
).and_return(
|
||||||
@@ -320,32 +304,26 @@ def test_get_borgmatic_state_directory_falls_back_to_state_directory():
|
|||||||
assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
|
assert module.get_borgmatic_state_directory({}) == '/tmp/borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_get_borgmatic_state_directory_defaults_to_hard_coded_path():
|
def test_get_borgmatic_state_directory_defaults_to_hard_coded_path(monkeypatch):
|
||||||
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
flexmock(module).should_receive('expand_user_in_path').replace_with(lambda path: path)
|
||||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
monkeypatch.delenv('XDG_STATE_HOME', raising=False)
|
||||||
assert module.get_borgmatic_state_directory({}) == '~/.local/state/borgmatic'
|
assert module.get_borgmatic_state_directory({}) == '~/.local/state/borgmatic'
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_systemd_directory_none():
|
def test_resolve_systemd_directory_none(monkeypatch):
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
|
monkeypatch.delenv('RUNTIME_DIRECTORY', raising=False)
|
||||||
None
|
monkeypatch.delenv('STATE_DIRECTORY', raising=False)
|
||||||
)
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(None)
|
|
||||||
|
|
||||||
assert module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY) is None
|
assert module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY) is None
|
||||||
assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) is None
|
assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) is None
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_systemd_directory_single():
|
def test_resolve_systemd_directory_single(monkeypatch):
|
||||||
runtime_dir = '/run/borgmatic'
|
runtime_dir = '/run/borgmatic'
|
||||||
state_dir = '/var/lib/borgmatic'
|
state_dir = '/var/lib/borgmatic'
|
||||||
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
|
monkeypatch.setenv('RUNTIME_DIRECTORY', runtime_dir)
|
||||||
runtime_dir
|
monkeypatch.setenv('STATE_DIRECTORY', state_dir)
|
||||||
)
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(
|
|
||||||
state_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY)
|
module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY)
|
||||||
@@ -354,16 +332,12 @@ def test_resolve_systemd_directory_single():
|
|||||||
assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) == state_dir
|
assert module.resolve_systemd_directory(module.Systemd_directories.STATE_DIRECTORY) == state_dir
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_systemd_directory_multiple():
|
def test_resolve_systemd_directory_multiple(monkeypatch):
|
||||||
runtime_dirs = '/run/borgmatic:/run/second:/run/third'
|
runtime_dirs = '/run/borgmatic:/run/second:/run/third'
|
||||||
state_dirs = '/var/lib/borgmatic:/var/lib/second:/var/lib/third'
|
state_dirs = '/var/lib/borgmatic:/var/lib/second:/var/lib/third'
|
||||||
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('RUNTIME_DIRECTORY').and_return(
|
monkeypatch.setenv('RUNTIME_DIRECTORY', runtime_dirs)
|
||||||
runtime_dirs
|
monkeypatch.setenv('STATE_DIRECTORY', state_dirs)
|
||||||
)
|
|
||||||
flexmock(module.os.environ).should_receive('get').with_args('STATE_DIRECTORY').and_return(
|
|
||||||
state_dirs
|
|
||||||
)
|
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY)
|
module.resolve_systemd_directory(module.Systemd_directories.RUNTIME_DIRECTORY)
|
||||||
|
|||||||
@@ -66,9 +66,7 @@ def test_initialize_monitor_without_send_logs_does_not_add_handler():
|
|||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_respects_dry_run():
|
def test_ping_monitor_respects_dry_run():
|
||||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||||
'resolve_credential'
|
|
||||||
).replace_with(lambda url, config: url)
|
|
||||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||||
'format_buffered_logs_for_payload',
|
'format_buffered_logs_for_payload',
|
||||||
).and_return('loggy log')
|
).and_return('loggy log')
|
||||||
@@ -85,9 +83,7 @@ def test_ping_monitor_respects_dry_run():
|
|||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_with_no_states_does_not_notify():
|
def test_ping_monitor_with_no_states_does_not_notify():
|
||||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler').never()
|
||||||
'resolve_credential'
|
|
||||||
).replace_with(lambda url, config: url)
|
|
||||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||||
'format_buffered_logs_for_payload',
|
'format_buffered_logs_for_payload',
|
||||||
).never()
|
).never()
|
||||||
@@ -104,9 +100,7 @@ def test_ping_monitor_with_no_states_does_not_notify():
|
|||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_notifies_fail_by_default():
|
def test_ping_monitor_notifies_fail_by_default():
|
||||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||||
'resolve_credential'
|
|
||||||
).replace_with(lambda url, config: url)
|
|
||||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||||
'format_buffered_logs_for_payload',
|
'format_buffered_logs_for_payload',
|
||||||
).and_return('')
|
).and_return('')
|
||||||
@@ -129,9 +123,7 @@ def test_ping_monitor_notifies_fail_by_default():
|
|||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_with_logs_appends_logs_to_body():
|
def test_ping_monitor_with_logs_appends_logs_to_body():
|
||||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||||
'resolve_credential'
|
|
||||||
).replace_with(lambda url, config: url)
|
|
||||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||||
'format_buffered_logs_for_payload',
|
'format_buffered_logs_for_payload',
|
||||||
).and_return('loggy log')
|
).and_return('loggy log')
|
||||||
@@ -154,9 +146,7 @@ def test_ping_monitor_with_logs_appends_logs_to_body():
|
|||||||
|
|
||||||
|
|
||||||
def test_ping_monitor_with_finish_default_config_notifies():
|
def test_ping_monitor_with_finish_default_config_notifies():
|
||||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive('get_handler')
|
||||||
'resolve_credential'
|
|
||||||
).replace_with(lambda url, config: url)
|
|
||||||
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
flexmock(module.borgmatic.hooks.monitoring.logs).should_receive(
|
||||||
'format_buffered_logs_for_payload',
|
'format_buffered_logs_for_payload',
|
||||||
).and_return('')
|
).and_return('')
|
||||||
|
|||||||
@@ -779,7 +779,6 @@ def test_raise_for_process_errors_with_no_processes_bails():
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas={},
|
process_metadatas={},
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -796,7 +795,6 @@ def test_raise_for_process_errors_with_running_process_bails():
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -814,7 +812,6 @@ def test_raise_for_process_errors_with_running_process_and_no_buffer_readers_wai
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers={},
|
buffer_readers={},
|
||||||
process_metadatas=process_metadatas,
|
process_metadatas=process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -832,7 +829,6 @@ def test_raise_for_process_errors_with_successful_process_bails():
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -850,7 +846,6 @@ def test_raise_for_process_errors_with_warning_process_returns_warning_status():
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -865,7 +860,6 @@ def test_raise_for_process_errors_with_error_process_raises():
|
|||||||
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
||||||
}
|
}
|
||||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
command = flexmock()
|
command = flexmock()
|
||||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||||
|
|
||||||
@@ -873,7 +867,6 @@ def test_raise_for_process_errors_with_error_process_raises():
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -897,14 +890,12 @@ def test_raise_for_process_errors_with_success_process_and_warning_process_retur
|
|||||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||||
object, 1, object, object
|
object, 1, object, object
|
||||||
).and_return(module.Exit_status.WARNING)
|
).and_return(module.Exit_status.WARNING)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
flexmock(module).should_receive('command_for_process').never()
|
flexmock(module).should_receive('command_for_process').never()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -926,7 +917,6 @@ def test_raise_for_process_errors_with_warning_process_and_error_process_raises(
|
|||||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||||
object, 3, object, object
|
object, 3, object, object
|
||||||
).and_return(module.Exit_status.ERROR)
|
).and_return(module.Exit_status.ERROR)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
command = flexmock()
|
command = flexmock()
|
||||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||||
|
|
||||||
@@ -934,7 +924,6 @@ def test_raise_for_process_errors_with_warning_process_and_error_process_raises(
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -958,7 +947,6 @@ def test_raise_for_process_errors_with_warning_process_and_running_process_kills
|
|||||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||||
object, 1, object, object
|
object, 1, object, object
|
||||||
).and_return(module.Exit_status.WARNING)
|
).and_return(module.Exit_status.WARNING)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
command = flexmock()
|
command = flexmock()
|
||||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||||
|
|
||||||
@@ -966,7 +954,6 @@ def test_raise_for_process_errors_with_warning_process_and_running_process_kills
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -988,7 +975,6 @@ def test_raise_for_process_errors_with_error_process_and_running_process_kills_a
|
|||||||
flexmock(module).should_receive('interpret_exit_code').with_args(
|
flexmock(module).should_receive('interpret_exit_code').with_args(
|
||||||
object, 3, object, object
|
object, 3, object, object
|
||||||
).and_return(module.Exit_status.ERROR)
|
).and_return(module.Exit_status.ERROR)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
command = flexmock()
|
command = flexmock()
|
||||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||||
|
|
||||||
@@ -996,7 +982,6 @@ def test_raise_for_process_errors_with_error_process_and_running_process_kills_a
|
|||||||
module.raise_for_process_errors(
|
module.raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
@@ -1013,7 +998,6 @@ def test_raise_for_process_errors_with_warning_process_and_long_output_raises_wi
|
|||||||
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
process: module.Process_metadata(last_lines=['hi', 'there'], capture=False)
|
||||||
}
|
}
|
||||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.ERROR)
|
||||||
flexmock(module).should_receive('log_remaining_buffer_lines').and_return(())
|
|
||||||
command = flexmock()
|
command = flexmock()
|
||||||
flexmock(module).should_receive('command_for_process').and_return(command)
|
flexmock(module).should_receive('command_for_process').and_return(command)
|
||||||
|
|
||||||
@@ -1021,7 +1005,6 @@ def test_raise_for_process_errors_with_warning_process_and_long_output_raises_wi
|
|||||||
flexmock(module, ERROR_OUTPUT_MAX_LINE_COUNT=2).raise_for_process_errors(
|
flexmock(module, ERROR_OUTPUT_MAX_LINE_COUNT=2).raise_for_process_errors(
|
||||||
buffer_readers,
|
buffer_readers,
|
||||||
process_metadatas,
|
process_metadatas,
|
||||||
output_log_level=None,
|
|
||||||
borg_local_path=flexmock(),
|
borg_local_path=flexmock(),
|
||||||
borg_exit_codes=flexmock(),
|
borg_exit_codes=flexmock(),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user