Compare commits
35 commits
1.5.4
...
1.5.7.dev0
| Author | SHA1 | Date | |
|---|---|---|---|
| 37cc229749 | |||
| 17c2d109e5 | |||
| c8d5de2179 | |||
| 32e15dc905 | |||
| f5ebca4907 | |||
| 01db676d68 | |||
| d2d92b1f1a | |||
| 27cbe9dfc0 | |||
| 8fb830099f | |||
| 463a133a63 | |||
| a16fed8887 | |||
| 33113890f5 | |||
| abd47fc14e | |||
| 7fb4061759 | |||
| b320e74ad5 | |||
| 0ed8f67b9d | |||
| a12a1121b6 | |||
| 795e18773b | |||
| aa14449857 | |||
| ed7b1cd3d7 | |||
| a155eefa23 | |||
| 398665be9e | |||
| 6db232d4ac | |||
| d7277893fb | |||
| 00033bf0a8 | |||
| adda33dc4e | |||
| 097a09578a | |||
| 65472c8de2 | |||
| 602ad9e7ee | |||
| 96df52ec50 | |||
| 244dc35bae | |||
| d9c9d7d2ee | |||
| 89cb5eb76d | |||
| 6d3802335e | |||
| c1d6232b79 |
32 changed files with 1098 additions and 422 deletions
37
NEWS
37
NEWS
|
|
@ -1,3 +1,40 @@
|
|||
1.5.7.dev0
|
||||
* #327: Fix broken pass-through of BORG_* environment variables to Borg.
|
||||
* #328: Fix duplicate logging to Healthchecks and send "after_*" hooks output to Healthchecks.
|
||||
* #331: Add SSL support to PostgreSQL database configuration.
|
||||
* #333: Fix for potential data loss (data not getting backed up) when borgmatic omitted configured
|
||||
source directories in certain situations. Specifically, this occurred when two source directories
|
||||
on different filesystems were related by parentage (e.g. "/foo" and "/foo/bar/baz") and the
|
||||
one_file_system option was enabled.
|
||||
* Update documentation code fragments theme to better match the rest of the page.
|
||||
* Improve configuration reference documentation readability via more aggressive word-wrapping in
|
||||
configuration schema descriptions.
|
||||
|
||||
1.5.6
|
||||
* #292: Allow before_backup and similiar hooks to exit with a soft failure without altering the
|
||||
monitoring status on Healthchecks or other providers. Support this by waiting to ping monitoring
|
||||
services with a "start" status until after before_* hooks finish. Failures in before_* hooks
|
||||
still trigger a monitoring "fail" status.
|
||||
* #316: Fix hang when a stale database dump named pipe from an aborted borgmatic run remains on
|
||||
disk.
|
||||
* #323: Fix for certain configuration options like ssh_command impacting Borg invocations for
|
||||
separate configuration files.
|
||||
* #324: Add "borgmatic extract --strip-components" flag to remove leading path components when
|
||||
extracting an archive.
|
||||
* Tweak comment indentation in generated configuration file for clarity.
|
||||
* Link to Borgmacator GNOME AppIndicator from monitoring documentation.
|
||||
|
||||
1.5.5
|
||||
* #314: Fix regression in support for PostgreSQL's "directory" dump format. Unlike other dump
|
||||
formats, the "directory" dump format does not stream directly to/from Borg.
|
||||
* #315: Fix enabled database hooks to implicitly set one_file_system configuration option to true.
|
||||
This prevents Borg from reading devices like /dev/zero and hanging.
|
||||
* #316: Fix hang when streaming a database dump to Borg with implicit duplicate source directories
|
||||
by deduplicating them first.
|
||||
* #319: Fix error message when there are no MySQL databases to dump for "all" databases.
|
||||
* Improve documentation around the installation process. Specifically, making borgmatic commands
|
||||
runnable via the system PATH and offering a global install option.
|
||||
|
||||
1.5.4
|
||||
* #310: Fix legitimate database dump command errors (exit code 1) not being treated as errors by
|
||||
borgmatic.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import glob
|
|||
import itertools
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import tempfile
|
||||
|
||||
from borgmatic.execute import DO_NOT_CAPTURE, execute_command, execute_command_with_processes
|
||||
|
|
@ -43,6 +44,53 @@ def _expand_home_directories(directories):
|
|||
return tuple(os.path.expanduser(directory) for directory in directories)
|
||||
|
||||
|
||||
def map_directories_to_devices(directories): # pragma: no cover
|
||||
'''
|
||||
Given a sequence of directories, return a map from directory to an identifier for the device on
|
||||
which that directory resides. This is handy for determining whether two different directories
|
||||
are on the same filesystem (have the same device identifier).
|
||||
'''
|
||||
return {directory: os.stat(directory).st_dev for directory in directories}
|
||||
|
||||
|
||||
def deduplicate_directories(directory_devices):
|
||||
'''
|
||||
Given a map from directory to the identifier for the device on which that directory resides,
|
||||
return the directories as a sorted tuple with all duplicate child directories removed. For
|
||||
instance, if paths is ('/foo', '/foo/bar'), return just: ('/foo',)
|
||||
|
||||
The one exception to this rule is if two paths are on different filesystems (devices). In that
|
||||
case, they won't get de-duplicated in case they both need to be passed to Borg (e.g. the
|
||||
location.one_file_system option is true).
|
||||
|
||||
The idea is that if Borg is given a parent directory, then it doesn't also need to be given
|
||||
child directories, because it will naturally spider the contents of the parent directory. And
|
||||
there are cases where Borg coming across the same file twice will result in duplicate reads and
|
||||
even hangs, e.g. when a database hook is using a named pipe for streaming database dumps to
|
||||
Borg.
|
||||
'''
|
||||
deduplicated = set()
|
||||
directories = sorted(directory_devices.keys())
|
||||
|
||||
for directory in directories:
|
||||
deduplicated.add(directory)
|
||||
parents = pathlib.PurePath(directory).parents
|
||||
|
||||
# If another directory in the given list is a parent of current directory (even n levels
|
||||
# up) and both are on the same filesystem, then the current directory is a duplicate.
|
||||
for other_directory in directories:
|
||||
for parent in parents:
|
||||
if (
|
||||
pathlib.PurePath(other_directory) == parent
|
||||
and directory_devices[other_directory] == directory_devices[directory]
|
||||
):
|
||||
if directory in deduplicated:
|
||||
deduplicated.remove(directory)
|
||||
break
|
||||
|
||||
return tuple(sorted(deduplicated))
|
||||
|
||||
|
||||
def _write_pattern_file(patterns=None):
|
||||
'''
|
||||
Given a sequence of patterns, write them to a named temporary file and return it. Return None
|
||||
|
|
@ -148,9 +196,13 @@ def create_archive(
|
|||
If a sequence of stream processes is given (instances of subprocess.Popen), then execute the
|
||||
create command while also triggering the given processes to produce output.
|
||||
'''
|
||||
sources = _expand_directories(
|
||||
location_config['source_directories']
|
||||
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
|
||||
sources = deduplicate_directories(
|
||||
map_directories_to_devices(
|
||||
_expand_directories(
|
||||
location_config['source_directories']
|
||||
+ borgmatic_source_directories(location_config.get('borgmatic_source_directory'))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
pattern_file = _write_pattern_file(location_config.get('patterns'))
|
||||
|
|
@ -175,7 +227,11 @@ def create_archive(
|
|||
+ (('--chunker-params', chunker_params) if chunker_params else ())
|
||||
+ (('--compression', compression) if compression else ())
|
||||
+ (('--remote-ratelimit', str(remote_rate_limit)) if remote_rate_limit else ())
|
||||
+ (('--one-file-system',) if location_config.get('one_file_system') else ())
|
||||
+ (
|
||||
('--one-file-system',)
|
||||
if location_config.get('one_file_system') or stream_processes
|
||||
else ()
|
||||
)
|
||||
+ (('--numeric-owner',) if location_config.get('numeric_owner') else ())
|
||||
+ (('--noatime',) if location_config.get('atime') is False else ())
|
||||
+ (('--noctime',) if location_config.get('ctime') is False else ())
|
||||
|
|
|
|||
|
|
@ -19,9 +19,15 @@ DEFAULT_BOOL_OPTION_TO_ENVIRONMENT_VARIABLE = {
|
|||
|
||||
def initialize(storage_config):
|
||||
for option_name, environment_variable_name in OPTION_TO_ENVIRONMENT_VARIABLE.items():
|
||||
value = storage_config.get(option_name)
|
||||
|
||||
# Options from borgmatic configuration take precedence over already set BORG_* environment
|
||||
# variables.
|
||||
value = storage_config.get(option_name) or os.environ.get(environment_variable_name)
|
||||
|
||||
if value:
|
||||
os.environ[environment_variable_name] = value
|
||||
else:
|
||||
os.environ.pop(environment_variable_name, None)
|
||||
|
||||
for (
|
||||
option_name,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ def extract_archive(
|
|||
local_path='borg',
|
||||
remote_path=None,
|
||||
destination_path=None,
|
||||
strip_components=None,
|
||||
progress=False,
|
||||
extract_to_stdout=False,
|
||||
):
|
||||
|
|
@ -91,6 +92,7 @@ def extract_archive(
|
|||
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
|
||||
+ (('--debug', '--list', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
|
||||
+ (('--dry-run',) if dry_run else ())
|
||||
+ (('--strip-components', str(strip_components)) if strip_components else ())
|
||||
+ (('--progress',) if progress else ())
|
||||
+ (('--stdout',) if extract_to_stdout else ())
|
||||
+ ('::'.join((repository if ':' in repository else os.path.abspath(repository), archive)),)
|
||||
|
|
|
|||
|
|
@ -340,6 +340,13 @@ def parse_arguments(*unparsed_arguments):
|
|||
dest='destination',
|
||||
help='Directory to extract files into, defaults to the current directory',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--strip-components',
|
||||
type=int,
|
||||
metavar='NUMBER',
|
||||
dest='strip_components',
|
||||
help='Number of leading path components to remove from each extracted path. Skip paths with fewer elements',
|
||||
)
|
||||
extract_group.add_argument(
|
||||
'--progress',
|
||||
dest='progress',
|
||||
|
|
|
|||
|
|
@ -59,11 +59,10 @@ def run_configuration(config_filename, config, arguments):
|
|||
try:
|
||||
if prune_create_or_check:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
'initialize_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitor.State.START,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
|
|
@ -91,6 +90,16 @@ def run_configuration(config_filename, config, arguments):
|
|||
'pre-check',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
if prune_create_or_check:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitor.State.START,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
|
|
@ -155,7 +164,7 @@ def run_configuration(config_filename, config, arguments):
|
|||
'post-check',
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
if {'prune', 'create', 'check'}.intersection(arguments):
|
||||
if prune_create_or_check:
|
||||
dispatch.call_hooks(
|
||||
'ping_monitor',
|
||||
hooks,
|
||||
|
|
@ -165,6 +174,14 @@ def run_configuration(config_filename, config, arguments):
|
|||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
dispatch.call_hooks(
|
||||
'destroy_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
|
|
@ -195,6 +212,14 @@ def run_configuration(config_filename, config, arguments):
|
|||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
dispatch.call_hooks(
|
||||
'destroy_monitor',
|
||||
hooks,
|
||||
config_filename,
|
||||
monitor.MONITOR_HOOK_NAMES,
|
||||
monitoring_log_level,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
except (OSError, CalledProcessError) as error:
|
||||
if command.considered_soft_failure(config_filename, error):
|
||||
return
|
||||
|
|
@ -254,6 +279,14 @@ def run_actions(
|
|||
)
|
||||
if 'create' in arguments:
|
||||
logger.info('{}: Creating archive{}'.format(repository, dry_run_label))
|
||||
dispatch.call_hooks(
|
||||
'remove_database_dumps',
|
||||
hooks,
|
||||
repository,
|
||||
dump.DATABASE_HOOK_NAMES,
|
||||
location,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
active_dumps = dispatch.call_hooks(
|
||||
'dump_databases',
|
||||
hooks,
|
||||
|
|
@ -311,6 +344,7 @@ def run_actions(
|
|||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
destination_path=arguments['extract'].destination,
|
||||
strip_components=arguments['extract'].strip_components,
|
||||
progress=arguments['extract'].progress,
|
||||
)
|
||||
if 'mount' in arguments:
|
||||
|
|
@ -346,6 +380,14 @@ def run_actions(
|
|||
repository, arguments['restore'].archive
|
||||
)
|
||||
)
|
||||
dispatch.call_hooks(
|
||||
'remove_database_dumps',
|
||||
hooks,
|
||||
repository,
|
||||
dump.DATABASE_HOOK_NAMES,
|
||||
location,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
|
||||
restore_names = arguments['restore'].databases or []
|
||||
if 'all' in restore_names:
|
||||
|
|
@ -386,10 +428,12 @@ def run_actions(
|
|||
local_path=local_path,
|
||||
remote_path=remote_path,
|
||||
destination_path='/',
|
||||
extract_to_stdout=True,
|
||||
# A directory format dump isn't a single file, and therefore can't extract
|
||||
# to stdout. In this case, the extract_process return value is None.
|
||||
extract_to_stdout=bool(restore_database.get('format') != 'directory'),
|
||||
)
|
||||
|
||||
# Run a single database restore, consuming the extract stdout.
|
||||
# Run a single database restore, consuming the extract stdout (if any).
|
||||
dispatch.call_hooks(
|
||||
'restore_database_dump',
|
||||
{hook_name: [restore_database]},
|
||||
|
|
@ -400,6 +444,15 @@ def run_actions(
|
|||
extract_process,
|
||||
)
|
||||
|
||||
dispatch.call_hooks(
|
||||
'remove_database_dumps',
|
||||
hooks,
|
||||
repository,
|
||||
dump.DATABASE_HOOK_NAMES,
|
||||
location,
|
||||
global_arguments.dry_run,
|
||||
)
|
||||
|
||||
if not restore_names and not found_names:
|
||||
raise ValueError('No databases were found to restore')
|
||||
|
||||
|
|
|
|||
|
|
@ -37,9 +37,7 @@ def _schema_to_sample_configuration(schema, level=0, parent_is_sequence=False):
|
|||
for item_schema in schema['seq']
|
||||
]
|
||||
)
|
||||
add_comments_to_configuration_sequence(
|
||||
config, schema, indent=(level * INDENT) + SEQUENCE_INDENT
|
||||
)
|
||||
add_comments_to_configuration_sequence(config, schema, indent=(level * INDENT))
|
||||
elif 'map' in schema:
|
||||
config = yaml.comments.CommentedMap(
|
||||
[
|
||||
|
|
@ -86,8 +84,8 @@ def _comment_out_optional_configuration(rendered_config):
|
|||
optional = False
|
||||
|
||||
for line in rendered_config.split('\n'):
|
||||
# Upon encountering an optional configuration option, commenting out lines until the next
|
||||
# blank line.
|
||||
# Upon encountering an optional configuration option, comment out lines until the next blank
|
||||
# line.
|
||||
if line.strip().startswith('# {}'.format(COMMENTED_OUT_SENTINEL)):
|
||||
optional = True
|
||||
continue
|
||||
|
|
@ -142,7 +140,7 @@ def add_comments_to_configuration_sequence(config, schema, indent=0):
|
|||
|
||||
```
|
||||
things:
|
||||
# First key description. Added by this function.
|
||||
# First key description. Added by this function.
|
||||
- key: foo
|
||||
# Second key description. Added by add_comments_to_configuration_map().
|
||||
other: bar
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ version: 1
|
|||
map:
|
||||
location:
|
||||
desc: |
|
||||
Where to look for files to backup, and where to store those backups. See
|
||||
https://borgbackup.readthedocs.io/en/stable/quickstart.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create for details.
|
||||
Where to look for files to backup, and where to store those backups.
|
||||
See https://borgbackup.readthedocs.io/en/stable/quickstart.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details.
|
||||
required: true
|
||||
map:
|
||||
source_directories:
|
||||
|
|
@ -13,7 +14,8 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of source directories to backup (required). Globs and tildes are expanded.
|
||||
List of source directories to backup (required). Globs and
|
||||
tildes are expanded.
|
||||
example:
|
||||
- /home
|
||||
- /etc
|
||||
|
|
@ -23,18 +25,25 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Paths to local or remote repositories (required). Tildes are expanded. Multiple
|
||||
repositories are backed up to in sequence. See ssh_command for SSH options like
|
||||
identity file or port.
|
||||
Paths to local or remote repositories (required). Tildes are
|
||||
expanded. Multiple repositories are backed up to in
|
||||
sequence. See ssh_command for SSH options like identity file
|
||||
or port.
|
||||
example:
|
||||
- user@backupserver:sourcehostname.borg
|
||||
one_file_system:
|
||||
type: bool
|
||||
desc: Stay in same file system (do not cross mount points). Defaults to false.
|
||||
desc: |
|
||||
Stay in same file system (do not cross mount points).
|
||||
Defaults to false. But when a database hook is used, the
|
||||
setting here is ignored and one_file_system is considered
|
||||
true.
|
||||
example: true
|
||||
numeric_owner:
|
||||
type: bool
|
||||
desc: Only store/extract numeric user and group identifiers. Defaults to false.
|
||||
desc: |
|
||||
Only store/extract numeric user and group identifiers.
|
||||
Defaults to false.
|
||||
example: true
|
||||
atime:
|
||||
type: bool
|
||||
|
|
@ -46,25 +55,32 @@ map:
|
|||
example: false
|
||||
birthtime:
|
||||
type: bool
|
||||
desc: Store birthtime (creation date) into archive. Defaults to true.
|
||||
desc: |
|
||||
Store birthtime (creation date) into archive. Defaults to
|
||||
true.
|
||||
example: false
|
||||
read_special:
|
||||
type: bool
|
||||
desc: |
|
||||
Use Borg's --read-special flag to allow backup of block and other special
|
||||
devices. Use with caution, as it will lead to problems if used when
|
||||
backing up special devices such as /dev/zero. Defaults to false.
|
||||
Use Borg's --read-special flag to allow backup of block and
|
||||
other special devices. Use with caution, as it will lead to
|
||||
problems if used when backing up special devices such as
|
||||
/dev/zero. Defaults to false. But when a database hook is
|
||||
used, the setting here is ignored and read_special is
|
||||
considered true.
|
||||
example: false
|
||||
bsd_flags:
|
||||
type: bool
|
||||
desc: Record bsdflags (e.g. NODUMP, IMMUTABLE) in archive. Defaults to true.
|
||||
desc: |
|
||||
Record bsdflags (e.g. NODUMP, IMMUTABLE) in archive.
|
||||
Defaults to true.
|
||||
example: true
|
||||
files_cache:
|
||||
type: str
|
||||
desc: |
|
||||
Mode in which to operate the files cache. See
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html#description for
|
||||
details. Defaults to "ctime,size,inode".
|
||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details. Defaults to "ctime,size,inode".
|
||||
example: ctime,size,inode
|
||||
local_path:
|
||||
type: str
|
||||
|
|
@ -78,9 +94,10 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Any paths matching these patterns are included/excluded from backups. Globs are
|
||||
expanded. (Tildes are not.) Note that Borg considers this option experimental.
|
||||
See the output of "borg help patterns" for more details. Quote any value if it
|
||||
Any paths matching these patterns are included/excluded from
|
||||
backups. Globs are expanded. (Tildes are not.) Note that
|
||||
Borg considers this option experimental. See the output of
|
||||
"borg help patterns" for more details. Quote any value if it
|
||||
contains leading punctuation, so it parses correctly.
|
||||
example:
|
||||
- 'R /'
|
||||
|
|
@ -91,17 +108,19 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Read include/exclude patterns from one or more separate named files, one pattern
|
||||
per line. Note that Borg considers this option experimental. See the output of
|
||||
"borg help patterns" for more details.
|
||||
Read include/exclude patterns from one or more separate
|
||||
named files, one pattern per line. Note that Borg considers
|
||||
this option experimental. See the output of "borg help
|
||||
patterns" for more details.
|
||||
example:
|
||||
- /etc/borgmatic/patterns
|
||||
exclude_patterns:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Any paths matching these patterns are excluded from backups. Globs and tildes
|
||||
are expanded. See the output of "borg help patterns" for more details.
|
||||
Any paths matching these patterns are excluded from backups.
|
||||
Globs and tildes are expanded. See the output of "borg help
|
||||
patterns" for more details.
|
||||
example:
|
||||
- '*.pyc'
|
||||
- ~/*/.cache
|
||||
|
|
@ -110,29 +129,32 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Read exclude patterns from one or more separate named files, one pattern per
|
||||
line. See the output of "borg help patterns" for more details.
|
||||
Read exclude patterns from one or more separate named files,
|
||||
one pattern per line. See the output of "borg help patterns"
|
||||
for more details.
|
||||
example:
|
||||
- /etc/borgmatic/excludes
|
||||
exclude_caches:
|
||||
type: bool
|
||||
desc: |
|
||||
Exclude directories that contain a CACHEDIR.TAG file. See
|
||||
http://www.brynosaurus.com/cachedir/spec.html for details. Defaults to false.
|
||||
http://www.brynosaurus.com/cachedir/spec.html for details.
|
||||
Defaults to false.
|
||||
example: true
|
||||
exclude_if_present:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Exclude directories that contain a file with the given filenames. Defaults to not
|
||||
set.
|
||||
Exclude directories that contain a file with the given
|
||||
filenames. Defaults to not set.
|
||||
example:
|
||||
- .nobackup
|
||||
keep_exclude_tags:
|
||||
type: bool
|
||||
desc: |
|
||||
If true, the exclude_if_present filename is included in backups. Defaults to
|
||||
false, meaning that the exclude_if_present filename is omitted from backups.
|
||||
If true, the exclude_if_present filename is included in
|
||||
backups. Defaults to false, meaning that the
|
||||
exclude_if_present filename is omitted from backups.
|
||||
example: true
|
||||
exclude_nodump:
|
||||
type: bool
|
||||
|
|
@ -142,90 +164,103 @@ map:
|
|||
borgmatic_source_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for additional source files used for temporary internal state like
|
||||
borgmatic database dumps. Note that changing this path prevents "borgmatic
|
||||
restore" from finding any database dumps created before the change. Defaults
|
||||
to ~/.borgmatic
|
||||
Path for additional source files used for temporary internal
|
||||
state like borgmatic database dumps. Note that changing this
|
||||
path prevents "borgmatic restore" from finding any database
|
||||
dumps created before the change. Defaults to ~/.borgmatic
|
||||
example: /tmp/borgmatic
|
||||
storage:
|
||||
desc: |
|
||||
Repository storage options. See
|
||||
https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/create.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/general.html for
|
||||
details.
|
||||
map:
|
||||
encryption_passcommand:
|
||||
type: str
|
||||
desc: |
|
||||
The standard output of this command is used to unlock the encryption key. Only
|
||||
use on repositories that were initialized with passcommand/repokey encryption.
|
||||
Note that if both encryption_passcommand and encryption_passphrase are set,
|
||||
then encryption_passphrase takes precedence. Defaults to not set.
|
||||
The standard output of this command is used to unlock the
|
||||
encryption key. Only use on repositories that were
|
||||
initialized with passcommand/repokey encryption. Note that
|
||||
if both encryption_passcommand and encryption_passphrase are
|
||||
set, then encryption_passphrase takes precedence. Defaults
|
||||
to not set.
|
||||
example: "secret-tool lookup borg-repository repo-name"
|
||||
encryption_passphrase:
|
||||
type: str
|
||||
desc: |
|
||||
Passphrase to unlock the encryption key with. Only use on repositories that were
|
||||
initialized with passphrase/repokey encryption. Quote the value if it contains
|
||||
punctuation, so it parses correctly. And backslash any quote or backslash
|
||||
Passphrase to unlock the encryption key with. Only use on
|
||||
repositories that were initialized with passphrase/repokey
|
||||
encryption. Quote the value if it contains punctuation, so
|
||||
it parses correctly. And backslash any quote or backslash
|
||||
literals as well. Defaults to not set.
|
||||
example: "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||
checkpoint_interval:
|
||||
type: int
|
||||
desc: |
|
||||
Number of seconds between each checkpoint during a long-running backup. See
|
||||
https://borgbackup.readthedocs.io/en/stable/faq.html#if-a-backup-stops-mid-way-does-the-already-backed-up-data-stay-there
|
||||
for details. Defaults to checkpoints every 1800 seconds (30 minutes).
|
||||
Number of seconds between each checkpoint during a
|
||||
long-running backup. See
|
||||
https://borgbackup.readthedocs.io/en/stable/faq.html
|
||||
for details. Defaults to checkpoints every 1800 seconds (30
|
||||
minutes).
|
||||
example: 1800
|
||||
chunker_params:
|
||||
type: str
|
||||
desc: |
|
||||
Specify the parameters passed to then chunker (CHUNK_MIN_EXP, CHUNK_MAX_EXP,
|
||||
HASH_MASK_BITS, HASH_WINDOW_SIZE). See https://borgbackup.readthedocs.io/en/stable/internals.html
|
||||
Specify the parameters passed to then chunker
|
||||
(CHUNK_MIN_EXP, CHUNK_MAX_EXP, HASH_MASK_BITS,
|
||||
HASH_WINDOW_SIZE). See
|
||||
https://borgbackup.readthedocs.io/en/stable/internals.html
|
||||
for details. Defaults to "19,23,21,4095".
|
||||
example: 19,23,21,4095
|
||||
compression:
|
||||
type: str
|
||||
desc: |
|
||||
Type of compression to use when creating archives. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-create for details.
|
||||
Defaults to "lz4".
|
||||
http://borgbackup.readthedocs.io/en/stable/usage/create.html
|
||||
for details. Defaults to "lz4".
|
||||
example: lz4
|
||||
remote_rate_limit:
|
||||
type: int
|
||||
desc: Remote network upload rate limit in kiBytes/second. Defaults to unlimited.
|
||||
desc: |
|
||||
Remote network upload rate limit in kiBytes/second. Defaults
|
||||
to unlimited.
|
||||
example: 100
|
||||
ssh_command:
|
||||
type: str
|
||||
desc: |
|
||||
Command to use instead of "ssh". This can be used to specify ssh options.
|
||||
Defaults to not set.
|
||||
Command to use instead of "ssh". This can be used to specify
|
||||
ssh options. Defaults to not set.
|
||||
example: ssh -i /path/to/private/key
|
||||
borg_base_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Base path used for various Borg directories. Defaults to $HOME, ~$USER, or ~.
|
||||
See https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for details.
|
||||
Base path used for various Borg directories. Defaults to
|
||||
$HOME, ~$USER, or ~.
|
||||
example: /path/to/base
|
||||
borg_config_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg configuration files. Defaults to $borg_base_directory/.config/borg
|
||||
Path for Borg configuration files. Defaults to
|
||||
$borg_base_directory/.config/borg
|
||||
example: /path/to/base/config
|
||||
borg_cache_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg cache files. Defaults to $borg_base_directory/.cache/borg
|
||||
Path for Borg cache files. Defaults to
|
||||
$borg_base_directory/.cache/borg
|
||||
example: /path/to/base/cache
|
||||
borg_security_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg security and encryption nonce files. Defaults to $borg_base_directory/.config/borg/security
|
||||
Path for Borg security and encryption nonce files. Defaults
|
||||
to $borg_base_directory/.config/borg/security
|
||||
example: /path/to/base/config/security
|
||||
borg_keys_directory:
|
||||
type: str
|
||||
desc: |
|
||||
Path for Borg encryption key files. Defaults to $borg_base_directory/.config/borg/keys
|
||||
Path for Borg encryption key files. Defaults to
|
||||
$borg_base_directory/.config/borg/keys
|
||||
example: /path/to/base/config/keys
|
||||
umask:
|
||||
type: scalar
|
||||
|
|
@ -233,58 +268,69 @@ map:
|
|||
example: 0077
|
||||
lock_wait:
|
||||
type: int
|
||||
desc: Maximum seconds to wait for acquiring a repository/cache lock. Defaults to 1.
|
||||
desc: |
|
||||
Maximum seconds to wait for acquiring a repository/cache
|
||||
lock. Defaults to 1.
|
||||
example: 5
|
||||
archive_name_format:
|
||||
type: str
|
||||
desc: |
|
||||
Name of the archive. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to
|
||||
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
|
||||
also specify a prefix in the retention section to avoid accidental pruning of
|
||||
archives with a different archive name format. And you should also specify a
|
||||
Name of the archive. Borg placeholders can be used. See the
|
||||
output of "borg help placeholders" for details. Defaults to
|
||||
"{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this
|
||||
option, you must also specify a prefix in the retention
|
||||
section to avoid accidental pruning of archives with a
|
||||
different archive name format. And you should also specify a
|
||||
prefix in the consistency section as well.
|
||||
example: "{hostname}-documents-{now}"
|
||||
relocated_repo_access_is_ok:
|
||||
type: bool
|
||||
desc: Bypass Borg error about a repository that has been moved. Defaults to false.
|
||||
desc: |
|
||||
Bypass Borg error about a repository that has been moved.
|
||||
Defaults to false.
|
||||
example: true
|
||||
unknown_unencrypted_repo_access_is_ok:
|
||||
type: bool
|
||||
desc: |
|
||||
Bypass Borg error about a previously unknown unencrypted repository. Defaults to
|
||||
false.
|
||||
Bypass Borg error about a previously unknown unencrypted
|
||||
repository. Defaults to false.
|
||||
example: true
|
||||
extra_borg_options:
|
||||
map:
|
||||
init:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg init".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg init".
|
||||
example: "--make-parent-dirs"
|
||||
prune:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg prune".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg prune".
|
||||
example: "--save-space"
|
||||
create:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg create".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg create".
|
||||
example: "--no-files-cache"
|
||||
check:
|
||||
type: str
|
||||
desc: Extra command-line options to pass to "borg check".
|
||||
desc: |
|
||||
Extra command-line options to pass to "borg check".
|
||||
example: "--save-space"
|
||||
desc: |
|
||||
Additional options to pass directly to particular Borg commands, handy for Borg
|
||||
options that borgmatic does not yet support natively. Note that borgmatic does
|
||||
not perform any validation on these options. Running borgmatic with
|
||||
"--verbosity 2" shows the exact Borg command-line invocation.
|
||||
Additional options to pass directly to particular Borg
|
||||
commands, handy for Borg options that borgmatic does not yet
|
||||
support natively. Note that borgmatic does not perform any
|
||||
validation on these options. Running borgmatic with
|
||||
"--verbosity 2" shows the exact Borg command-line
|
||||
invocation.
|
||||
retention:
|
||||
desc: |
|
||||
Retention policy for how many backups to keep in each category. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details.
|
||||
At least one of the "keep" options is required for pruning to work. See
|
||||
https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/
|
||||
if you'd like to skip pruning entirely.
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/prune.html for
|
||||
details. At least one of the "keep" options is required for pruning
|
||||
to work. See borgmatic documentation if you'd like to skip pruning
|
||||
entirely.
|
||||
map:
|
||||
keep_within:
|
||||
type: str
|
||||
|
|
@ -321,27 +367,37 @@ map:
|
|||
prefix:
|
||||
type: str
|
||||
desc: |
|
||||
When pruning, only consider archive names starting with this prefix.
|
||||
Borg placeholders can be used. See the output of "borg help placeholders" for
|
||||
details. Defaults to "{hostname}-". Use an empty value to disable the default.
|
||||
When pruning, only consider archive names starting with this
|
||||
prefix. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to
|
||||
"{hostname}-". Use an empty value to disable the default.
|
||||
example: sourcehostname
|
||||
consistency:
|
||||
desc: |
|
||||
Consistency checks to run after backups. See
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check and
|
||||
https://borgbackup.readthedocs.org/en/stable/usage.html#borg-extract for details.
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/check.html and
|
||||
https://borgbackup.readthedocs.io/en/stable/usage/extract.html for
|
||||
details.
|
||||
map:
|
||||
checks:
|
||||
seq:
|
||||
- type: str
|
||||
enum: ['repository', 'archives', 'data', 'extract', 'disabled']
|
||||
enum: [
|
||||
'repository',
|
||||
'archives',
|
||||
'data',
|
||||
'extract',
|
||||
'disabled'
|
||||
]
|
||||
unique: true
|
||||
desc: |
|
||||
List of one or more consistency checks to run: "repository", "archives", "data",
|
||||
and/or "extract". Defaults to "repository" and "archives". Set to "disabled" to
|
||||
disable all consistency checks. "repository" checks the consistency of the
|
||||
repository, "archives" checks all of the archives, "data" verifies the integrity
|
||||
of the data within the archives, and "extract" does an extraction dry-run of the
|
||||
List of one or more consistency checks to run: "repository",
|
||||
"archives", "data", and/or "extract". Defaults to
|
||||
"repository" and "archives". Set to "disabled" to disable
|
||||
all consistency checks. "repository" checks the consistency
|
||||
of the repository, "archives" checks all of the archives,
|
||||
"data" verifies the integrity of the data within the
|
||||
archives, and "extract" does an extraction dry-run of the
|
||||
most recent archive. Note that "data" implies "archives".
|
||||
example:
|
||||
- repository
|
||||
|
|
@ -350,24 +406,29 @@ map:
|
|||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
Paths to a subset of the repositories in the location section on which to run
|
||||
consistency checks. Handy in case some of your repositories are very large, and
|
||||
so running consistency checks on them would take too long. Defaults to running
|
||||
consistency checks on all repositories configured in the location section.
|
||||
Paths to a subset of the repositories in the location
|
||||
section on which to run consistency checks. Handy in case
|
||||
some of your repositories are very large, and so running
|
||||
consistency checks on them would take too long. Defaults to
|
||||
running consistency checks on all repositories configured in
|
||||
the location section.
|
||||
example:
|
||||
- user@backupserver:sourcehostname.borg
|
||||
check_last:
|
||||
type: int
|
||||
desc: Restrict the number of checked archives to the last n. Applies only to the
|
||||
"archives" check. Defaults to checking all archives.
|
||||
desc: |
|
||||
Restrict the number of checked archives to the last n.
|
||||
Applies only to the "archives" check. Defaults to checking
|
||||
all archives.
|
||||
example: 3
|
||||
prefix:
|
||||
type: str
|
||||
desc: |
|
||||
When performing the "archives" check, only consider archive names starting with
|
||||
this prefix. Borg placeholders can be used. See the output of
|
||||
"borg help placeholders" for details. Defaults to "{hostname}-". Use an empty
|
||||
value to disable the default.
|
||||
When performing the "archives" check, only consider archive
|
||||
names starting with this prefix. Borg placeholders can be
|
||||
used. See the output of "borg help placeholders" for
|
||||
details. Defaults to "{hostname}-". Use an empty value to
|
||||
disable the default.
|
||||
example: sourcehostname
|
||||
output:
|
||||
desc: |
|
||||
|
|
@ -376,72 +437,73 @@ map:
|
|||
color:
|
||||
type: bool
|
||||
desc: |
|
||||
Apply color to console output. Can be overridden with --no-color command-line
|
||||
flag. Defaults to true.
|
||||
Apply color to console output. Can be overridden with
|
||||
--no-color command-line flag. Defaults to true.
|
||||
example: false
|
||||
hooks:
|
||||
desc: |
|
||||
Shell commands, scripts, or integrations to execute at various points during a borgmatic
|
||||
run. IMPORTANT: All provided commands and scripts are executed with user permissions of
|
||||
borgmatic. Do not forget to set secure permissions on this configuration file (chmod
|
||||
0600) as well as on any script called from a hook (chmod 0700) to prevent potential
|
||||
shell injection or privilege escalation.
|
||||
Shell commands, scripts, or integrations to execute at various
|
||||
points during a borgmatic run. IMPORTANT: All provided commands and
|
||||
scripts are executed with user permissions of borgmatic. Do not
|
||||
forget to set secure permissions on this configuration file (chmod
|
||||
0600) as well as on any script called from a hook (chmod 0700) to
|
||||
prevent potential shell injection or privilege escalation.
|
||||
map:
|
||||
before_backup:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before creating a
|
||||
backup, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
before creating a backup, run once per configuration file.
|
||||
example:
|
||||
- echo "Starting a backup."
|
||||
before_prune:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before pruning, run
|
||||
once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
before pruning, run once per configuration file.
|
||||
example:
|
||||
- echo "Starting pruning."
|
||||
before_check:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before consistency
|
||||
checks, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
before consistency checks, run once per configuration file.
|
||||
example:
|
||||
- echo "Starting checks."
|
||||
after_backup:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after creating a
|
||||
backup, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
after creating a backup, run once per configuration file.
|
||||
example:
|
||||
- echo "Finished a backup."
|
||||
after_prune:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after pruning, run once
|
||||
per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
after pruning, run once per configuration file.
|
||||
example:
|
||||
- echo "Finished pruning."
|
||||
after_check:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after consistency
|
||||
checks, run once per configuration file.
|
||||
List of one or more shell commands or scripts to execute
|
||||
after consistency checks, run once per configuration file.
|
||||
example:
|
||||
- echo "Finished checks."
|
||||
on_error:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute when an exception
|
||||
occurs during a "prune", "create", or "check" action or an associated
|
||||
before/after hook.
|
||||
List of one or more shell commands or scripts to execute
|
||||
when an exception occurs during a "prune", "create", or
|
||||
"check" action or an associated before/after hook.
|
||||
example:
|
||||
- echo "Error during prune/create/check."
|
||||
postgresql_databases:
|
||||
|
|
@ -451,14 +513,17 @@ map:
|
|||
required: true
|
||||
type: str
|
||||
desc: |
|
||||
Database name (required if using this hook). Or "all" to dump all
|
||||
databases on the host.
|
||||
Database name (required if using this hook). Or
|
||||
"all" to dump all databases on the host. Note
|
||||
that using this database hook implicitly enables
|
||||
both read_special and one_file_system (see
|
||||
above) to support dump and restore streaming.
|
||||
example: users
|
||||
hostname:
|
||||
type: str
|
||||
desc: |
|
||||
Database hostname to connect to. Defaults to connecting via local
|
||||
Unix socket.
|
||||
Database hostname to connect to. Defaults to
|
||||
connecting via local Unix socket.
|
||||
example: database.example.org
|
||||
port:
|
||||
type: int
|
||||
|
|
@ -467,39 +532,78 @@ map:
|
|||
username:
|
||||
type: str
|
||||
desc: |
|
||||
Username with which to connect to the database. Defaults to the
|
||||
username of the current user. You probably want to specify the
|
||||
"postgres" superuser here when the database name is "all".
|
||||
Username with which to connect to the database.
|
||||
Defaults to the username of the current user.
|
||||
You probably want to specify the "postgres"
|
||||
superuser here when the database name is "all".
|
||||
example: dbuser
|
||||
password:
|
||||
type: str
|
||||
desc: |
|
||||
Password with which to connect to the database. Omitting a password
|
||||
will only work if PostgreSQL is configured to trust the configured
|
||||
username without a password, or you create a ~/.pgpass file.
|
||||
Password with which to connect to the database.
|
||||
Omitting a password will only work if PostgreSQL
|
||||
is configured to trust the configured username
|
||||
without a password, or you create a ~/.pgpass
|
||||
file.
|
||||
example: trustsome1
|
||||
format:
|
||||
type: str
|
||||
enum: ['plain', 'custom', 'directory', 'tar']
|
||||
desc: |
|
||||
Database dump output format. One of "plain", "custom", "directory",
|
||||
or "tar". Defaults to "custom" (unlike raw pg_dump). See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
Note that format is ignored when the database name is "all".
|
||||
Database dump output format. One of "plain",
|
||||
"custom", "directory", or "tar". Defaults to
|
||||
"custom" (unlike raw pg_dump). See pg_dump
|
||||
documentation for details. Note that format is
|
||||
ignored when the database name is "all".
|
||||
example: directory
|
||||
ssl_mode:
|
||||
type: str
|
||||
enum: ['disable', 'allow', 'prefer',
|
||||
'require', 'verify-ca', 'verify-full']
|
||||
desc: |
|
||||
SSL mode to use to connect to the database
|
||||
server. One of "disable", "allow", "prefer",
|
||||
"require", "verify-ca" or "verify-full".
|
||||
Defaults to "disable".
|
||||
example: require
|
||||
ssl_cert:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a client certificate.
|
||||
example: "/root/.postgresql/postgresql.crt"
|
||||
ssl_key:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a private client key.
|
||||
example: "/root/.postgresql/postgresql.key"
|
||||
ssl_root_cert:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a root certificate containing a list of
|
||||
trusted certificate authorities.
|
||||
example: "/root/.postgresql/root.crt"
|
||||
ssl_crl:
|
||||
type: str
|
||||
desc: |
|
||||
Path to a certificate revocation list.
|
||||
example: "/root/.postgresql/root.crl"
|
||||
options:
|
||||
type: str
|
||||
desc: |
|
||||
Additional pg_dump/pg_dumpall options to pass directly to the dump
|
||||
command, without performing any validation on them. See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
Additional pg_dump/pg_dumpall options to pass
|
||||
directly to the dump command, without performing
|
||||
any validation on them. See pg_dump
|
||||
documentation for details.
|
||||
example: --role=someone
|
||||
desc: |
|
||||
List of one or more PostgreSQL databases to dump before creating a backup,
|
||||
run once per configuration file. The database dumps are added to your source
|
||||
directories at runtime, backed up, and then removed afterwards. Requires
|
||||
List of one or more PostgreSQL databases to dump before
|
||||
creating a backup, run once per configuration file. The
|
||||
database dumps are added to your source directories at
|
||||
runtime, backed up, and removed afterwards. Requires
|
||||
pg_dump/pg_dumpall/pg_restore commands. See
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html for details.
|
||||
https://www.postgresql.org/docs/current/app-pgdump.html and
|
||||
https://www.postgresql.org/docs/current/libpq-ssl.html for
|
||||
details.
|
||||
mysql_databases:
|
||||
seq:
|
||||
- map:
|
||||
|
|
@ -507,14 +611,17 @@ map:
|
|||
required: true
|
||||
type: str
|
||||
desc: |
|
||||
Database name (required if using this hook). Or "all" to dump all
|
||||
databases on the host.
|
||||
Database name (required if using this hook). Or
|
||||
"all" to dump all databases on the host. Note
|
||||
that using this database hook implicitly enables
|
||||
both read_special and one_file_system (see
|
||||
above) to support dump and restore streaming.
|
||||
example: users
|
||||
hostname:
|
||||
type: str
|
||||
desc: |
|
||||
Database hostname to connect to. Defaults to connecting via local
|
||||
Unix socket.
|
||||
Database hostname to connect to. Defaults to
|
||||
connecting via local Unix socket.
|
||||
example: database.example.org
|
||||
port:
|
||||
type: int
|
||||
|
|
@ -523,87 +630,92 @@ map:
|
|||
username:
|
||||
type: str
|
||||
desc: |
|
||||
Username with which to connect to the database. Defaults to the
|
||||
username of the current user.
|
||||
Username with which to connect to the database.
|
||||
Defaults to the username of the current user.
|
||||
example: dbuser
|
||||
password:
|
||||
type: str
|
||||
desc: |
|
||||
Password with which to connect to the database. Omitting a password
|
||||
will only work if MySQL is configured to trust the configured
|
||||
username without a password.
|
||||
Password with which to connect to the database.
|
||||
Omitting a password will only work if MySQL is
|
||||
configured to trust the configured username
|
||||
without a password.
|
||||
example: trustsome1
|
||||
options:
|
||||
type: str
|
||||
desc: |
|
||||
Additional mysqldump options to pass directly to the dump command,
|
||||
without performing any validation on them. See
|
||||
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
|
||||
https://mariadb.com/kb/en/library/mysqldump/ for details.
|
||||
Additional mysqldump options to pass directly to
|
||||
the dump command, without performing any
|
||||
validation on them. See mysqldump documentation
|
||||
for details.
|
||||
example: --skip-comments
|
||||
desc: |
|
||||
List of one or more MySQL/MariaDB databases to dump before creating a backup,
|
||||
run once per configuration file. The database dumps are added to your source
|
||||
directories at runtime, backed up, and then removed afterwards. Requires
|
||||
List of one or more MySQL/MariaDB databases to dump before
|
||||
creating a backup, run once per configuration file. The
|
||||
database dumps are added to your source directories at
|
||||
runtime, backed up, and removed afterwards. Requires
|
||||
mysqldump/mysql commands (from either MySQL or MariaDB). See
|
||||
https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html or
|
||||
https://mariadb.com/kb/en/library/mysqldump/ for details.
|
||||
healthchecks:
|
||||
type: str
|
||||
desc: |
|
||||
Healthchecks ping URL or UUID to notify when a backup begins, ends, or errors.
|
||||
Create an account at https://healthchecks.io if you'd like to use this service.
|
||||
See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#healthchecks-hook
|
||||
for details.
|
||||
Healthchecks ping URL or UUID to notify when a backup
|
||||
begins, ends, or errors. Create an account at
|
||||
https://healthchecks.io if you'd like to use this service.
|
||||
See borgmatic monitoring documentation for details.
|
||||
example:
|
||||
https://hc-ping.com/your-uuid-here
|
||||
cronitor:
|
||||
type: str
|
||||
desc: |
|
||||
Cronitor ping URL to notify when a backup begins, ends, or errors. Create an
|
||||
account at https://cronitor.io if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronitor-hook
|
||||
for details.
|
||||
Cronitor ping URL to notify when a backup begins, ends, or
|
||||
errors. Create an account at https://cronitor.io if you'd
|
||||
like to use this service. See borgmatic monitoring
|
||||
documentation for details.
|
||||
example:
|
||||
https://cronitor.link/d3x0c1
|
||||
pagerduty:
|
||||
type: str
|
||||
desc: |
|
||||
PagerDuty integration key used to notify PagerDuty when a backup errors. Create
|
||||
an account at https://www.pagerduty.com/ if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook
|
||||
for details.
|
||||
PagerDuty integration key used to notify PagerDuty when a
|
||||
backup errors. Create an account at
|
||||
https://www.pagerduty.com/ if you'd like to use this
|
||||
service. See borgmatic monitoring documentation for details.
|
||||
example:
|
||||
a177cad45bd374409f78906a810a3074
|
||||
cronhub:
|
||||
type: str
|
||||
desc: |
|
||||
Cronhub ping URL to notify when a backup begins, ends, or errors. Create an
|
||||
account at https://cronhub.io if you'd like to use this service. See
|
||||
https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#cronhub-hook for
|
||||
details.
|
||||
Cronhub ping URL to notify when a backup begins, ends, or
|
||||
errors. Create an account at https://cronhub.io if you'd
|
||||
like to use this service. See borgmatic monitoring
|
||||
documentation for details.
|
||||
example:
|
||||
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d031
|
||||
https://cronhub.io/start/1f5e3410-254c-11e8-b61d-55875966d01
|
||||
before_everything:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute before running all
|
||||
actions (if one of them is "create"). These are collected from all configuration
|
||||
files and then run once before all of them (prior to all actions).
|
||||
List of one or more shell commands or scripts to execute
|
||||
before running all actions (if one of them is "create").
|
||||
These are collected from all configuration files and then
|
||||
run once before all of them (prior to all actions).
|
||||
example:
|
||||
- echo "Starting actions."
|
||||
after_everything:
|
||||
seq:
|
||||
- type: str
|
||||
desc: |
|
||||
List of one or more shell commands or scripts to execute after running all
|
||||
actions (if one of them is "create"). These are collected from all configuration
|
||||
files and then run once before all of them (prior to all actions).
|
||||
List of one or more shell commands or scripts to execute
|
||||
after running all actions (if one of them is "create").
|
||||
These are collected from all configuration files and then
|
||||
run once before all of them (prior to all actions).
|
||||
example:
|
||||
- echo "Completed actions."
|
||||
umask:
|
||||
type: scalar
|
||||
desc: Umask used when executing hooks. Defaults to the umask that borgmatic is run with.
|
||||
desc: |
|
||||
Umask used when executing hooks. Defaults to the umask that
|
||||
borgmatic is run with.
|
||||
example: 0077
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ MONITOR_STATE_TO_CRONHUB = {
|
|||
}
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Cronhub URL, modified with the monitor.State. Use the given configuration
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@ MONITOR_STATE_TO_CRONITOR = {
|
|||
}
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(ping_url, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Cronitor URL, modified with the monitor.State. Use the given configuration
|
||||
|
|
|
|||
|
|
@ -33,55 +33,39 @@ def make_database_dump_filename(dump_path, name, hostname=None):
|
|||
return os.path.join(os.path.expanduser(dump_path), hostname or 'localhost', name)
|
||||
|
||||
|
||||
def create_parent_directory_for_dump(dump_path):
|
||||
'''
|
||||
Create a directory to contain the given dump path.
|
||||
'''
|
||||
os.makedirs(os.path.dirname(dump_path), mode=0o700, exist_ok=True)
|
||||
|
||||
|
||||
def create_named_pipe_for_dump(dump_path):
|
||||
'''
|
||||
Create a named pipe at the given dump path.
|
||||
'''
|
||||
os.makedirs(os.path.dirname(dump_path), mode=0o700, exist_ok=True)
|
||||
if os.path.exists(dump_path):
|
||||
os.remove(dump_path)
|
||||
|
||||
create_parent_directory_for_dump(dump_path)
|
||||
os.mkfifo(dump_path, mode=0o600)
|
||||
|
||||
|
||||
def remove_database_dumps(dump_path, databases, database_type_name, log_prefix, dry_run):
|
||||
def remove_database_dumps(dump_path, database_type_name, log_prefix, dry_run):
|
||||
'''
|
||||
Remove the database dumps for the given databases in the dump directory path. The databases are
|
||||
supplied as a sequence of dicts, one dict describing each database as per the configuration
|
||||
schema. Use the name of the database type and the log prefix in any log entries. If this is a
|
||||
dry run, then don't actually remove anything.
|
||||
Remove all database dumps in the given dump directory path (including the directory itself). If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
if not databases:
|
||||
logger.debug('{}: No {} databases configured'.format(log_prefix, database_type_name))
|
||||
return
|
||||
|
||||
dry_run_label = ' (dry run; not actually removing anything)' if dry_run else ''
|
||||
|
||||
logger.info(
|
||||
'{}: Removing {} database dumps{}'.format(log_prefix, database_type_name, dry_run_label)
|
||||
)
|
||||
|
||||
for database in databases:
|
||||
dump_filename = make_database_dump_filename(
|
||||
dump_path, database['name'], database.get('hostname')
|
||||
)
|
||||
expanded_path = os.path.expanduser(dump_path)
|
||||
|
||||
logger.debug(
|
||||
'{}: Removing {} database dump {} from {}{}'.format(
|
||||
log_prefix, database_type_name, database['name'], dump_filename, dry_run_label
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
continue
|
||||
if dry_run:
|
||||
return
|
||||
|
||||
if os.path.isdir(dump_filename):
|
||||
shutil.rmtree(dump_filename)
|
||||
else:
|
||||
os.remove(dump_filename)
|
||||
dump_file_dir = os.path.dirname(dump_filename)
|
||||
|
||||
if len(os.listdir(dump_file_dir)) == 0:
|
||||
os.rmdir(dump_file_dir)
|
||||
if os.path.exists(expanded_path):
|
||||
shutil.rmtree(expanded_path)
|
||||
|
||||
|
||||
def convert_glob_patterns_to_borg_patterns(patterns):
|
||||
|
|
|
|||
|
|
@ -65,20 +65,24 @@ def format_buffered_logs_for_payload():
|
|||
return payload
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
ping_url_or_uuid, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
Add a handler to the root logger that stores in memory the most recent logs emitted. That
|
||||
way, we can send them all to Healthchecks upon a finish or failure state.
|
||||
'''
|
||||
logging.getLogger().addHandler(
|
||||
Forgetful_buffering_handler(PAYLOAD_LIMIT_BYTES, monitoring_log_level)
|
||||
)
|
||||
|
||||
|
||||
def ping_monitor(ping_url_or_uuid, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Ping the given Healthchecks URL or UUID, modified with the monitor.State. Use the given
|
||||
configuration filename in any log entries, and log to Healthchecks with the giving log level.
|
||||
If this is a dry run, then don't actually ping anything.
|
||||
'''
|
||||
if state is monitor.State.START:
|
||||
# Add a handler to the root logger that stores in memory the most recent logs emitted. That
|
||||
# way, we can send them all to Healthchecks upon a finish or failure state.
|
||||
logging.getLogger().addHandler(
|
||||
Forgetful_buffering_handler(PAYLOAD_LIMIT_BYTES, monitoring_log_level)
|
||||
)
|
||||
payload = ''
|
||||
|
||||
ping_url = (
|
||||
ping_url_or_uuid
|
||||
if ping_url_or_uuid.startswith('http')
|
||||
|
|
@ -97,7 +101,21 @@ def ping_monitor(ping_url_or_uuid, config_filename, state, monitoring_log_level,
|
|||
|
||||
if state in (monitor.State.FINISH, monitor.State.FAIL):
|
||||
payload = format_buffered_logs_for_payload()
|
||||
else:
|
||||
payload = ''
|
||||
|
||||
if not dry_run:
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
requests.post(ping_url, data=payload.encode('utf-8'))
|
||||
|
||||
|
||||
def destroy_monitor(ping_url_or_uuid, config_filename, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Remove the monitor handler that was added to the root logger. This prevents the handler from
|
||||
getting reused by other instances of this monitor.
|
||||
'''
|
||||
logger = logging.getLogger()
|
||||
|
||||
for handler in tuple(logger.handlers):
|
||||
if isinstance(handler, Forgetful_buffering_handler):
|
||||
logger.removeHandler(handler)
|
||||
|
|
|
|||
|
|
@ -73,9 +73,11 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
make_dump_path(location_config), requested_name, database.get('hostname')
|
||||
)
|
||||
extra_environment = {'MYSQL_PWD': database['password']} if 'password' in database else None
|
||||
dump_command_names = database_names_to_dump(
|
||||
dump_database_names = database_names_to_dump(
|
||||
database, extra_environment, log_prefix, dry_run_label
|
||||
)
|
||||
if not dump_database_names:
|
||||
raise ValueError('Cannot find any MySQL databases to dump.')
|
||||
|
||||
dump_command = (
|
||||
('mysqldump',)
|
||||
|
|
@ -86,7 +88,7 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
+ (('--user', database['username']) if 'username' in database else ())
|
||||
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
|
||||
+ ('--databases',)
|
||||
+ dump_command_names
|
||||
+ dump_database_names
|
||||
# Use shell redirection rather than execute_command(output_file=open(...)) to prevent
|
||||
# the open() call on a named pipe from hanging the main borgmatic process.
|
||||
+ ('>', dump_filename)
|
||||
|
|
@ -116,14 +118,11 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
|
||||
def remove_database_dumps(databases, log_prefix, location_config, dry_run): # pragma: no cover
|
||||
'''
|
||||
Remove the database dumps for the given databases. The databases are supplied as a sequence of
|
||||
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
|
||||
any log entries. Use the given location configuration dict to construct the destination path. If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
Remove all database dump files for this hook regardless of the given databases. Use the log
|
||||
prefix in any log entries. Use the given location configuration dict to construct the
|
||||
destination path. If this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
dump.remove_database_dumps(
|
||||
make_dump_path(location_config), databases, 'MySQL', log_prefix, dry_run
|
||||
)
|
||||
dump.remove_database_dumps(make_dump_path(location_config), 'MySQL', log_prefix, dry_run)
|
||||
|
||||
|
||||
def make_database_dump_pattern(
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@ logger = logging.getLogger(__name__)
|
|||
EVENTS_API_URL = 'https://events.pagerduty.com/v2/enqueue'
|
||||
|
||||
|
||||
def initialize_monitor(
|
||||
integration_key, config_filename, monitoring_log_level, dry_run
|
||||
): # pragma: no cover
|
||||
'''
|
||||
No initialization is necessary for this monitor.
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
def ping_monitor(integration_key, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
If this is an error state, create a PagerDuty event with the given integration key. Use the
|
||||
|
|
|
|||
|
|
@ -15,6 +15,25 @@ def make_dump_path(location_config): # pragma: no cover
|
|||
)
|
||||
|
||||
|
||||
def make_extra_environment(database):
|
||||
'''
|
||||
Make the extra_environment dict from the given database configuration.
|
||||
'''
|
||||
extra = dict()
|
||||
if 'password' in database:
|
||||
extra['PGPASSWORD'] = database['password']
|
||||
extra['PGSSLMODE'] = database.get('ssl_mode', 'disable')
|
||||
if 'ssl_cert' in database:
|
||||
extra['PGSSLCERT'] = database['ssl_cert']
|
||||
if 'ssl_key' in database:
|
||||
extra['PGSSLKEY'] = database['ssl_key']
|
||||
if 'ssl_root_cert' in database:
|
||||
extra['PGSSLROOTCERT'] = database['ssl_root_cert']
|
||||
if 'ssl_crl' in database:
|
||||
extra['PGSSLCRL'] = database['ssl_crl']
|
||||
return extra
|
||||
|
||||
|
||||
def dump_databases(databases, log_prefix, location_config, dry_run):
|
||||
'''
|
||||
Dump the given PostgreSQL databases to a named pipe. The databases are supplied as a sequence of
|
||||
|
|
@ -36,6 +55,7 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
make_dump_path(location_config), name, database.get('hostname')
|
||||
)
|
||||
all_databases = bool(name == 'all')
|
||||
dump_format = database.get('format', 'custom')
|
||||
command = (
|
||||
(
|
||||
'pg_dumpall' if all_databases else 'pg_dump',
|
||||
|
|
@ -46,14 +66,16 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--username', database['username']) if 'username' in database else ())
|
||||
+ (() if all_databases else ('--format', database.get('format', 'custom')))
|
||||
+ (() if all_databases else ('--format', dump_format))
|
||||
+ (('--file', dump_filename) if dump_format == 'directory' else ())
|
||||
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
|
||||
+ (() if all_databases else (name,))
|
||||
# Use shell redirection rather than the --file flag to sidestep synchronization issues
|
||||
# when pg_dump/pg_dumpall tries to write to a named pipe.
|
||||
+ ('>', dump_filename)
|
||||
# when pg_dump/pg_dumpall tries to write to a named pipe. But for the directory dump
|
||||
# format in a particular, a named destination is required, and redirection doesn't work.
|
||||
+ (('>', dump_filename) if dump_format != 'directory' else ())
|
||||
)
|
||||
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
|
||||
extra_environment = make_extra_environment(database)
|
||||
|
||||
logger.debug(
|
||||
'{}: Dumping PostgreSQL database {} to {}{}'.format(
|
||||
|
|
@ -63,7 +85,10 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
if dry_run:
|
||||
continue
|
||||
|
||||
dump.create_named_pipe_for_dump(dump_filename)
|
||||
if dump_format == 'directory':
|
||||
dump.create_parent_directory_for_dump(dump_filename)
|
||||
else:
|
||||
dump.create_named_pipe_for_dump(dump_filename)
|
||||
|
||||
processes.append(
|
||||
execute_command(
|
||||
|
|
@ -76,14 +101,11 @@ def dump_databases(databases, log_prefix, location_config, dry_run):
|
|||
|
||||
def remove_database_dumps(databases, log_prefix, location_config, dry_run): # pragma: no cover
|
||||
'''
|
||||
Remove the database dumps for the given databases. The databases are supplied as a sequence of
|
||||
dicts, one dict describing each database as per the configuration schema. Use the log prefix in
|
||||
any log entries. Use the given location configuration dict to construct the destination path. If
|
||||
this is a dry run, then don't actually remove anything.
|
||||
Remove all database dump files for this hook regardless of the given databases. Use the log
|
||||
prefix in any log entries. Use the given location configuration dict to construct the
|
||||
destination path. If this is a dry run, then don't actually remove anything.
|
||||
'''
|
||||
dump.remove_database_dumps(
|
||||
make_dump_path(location_config), databases, 'PostgreSQL', log_prefix, dry_run
|
||||
)
|
||||
dump.remove_database_dumps(make_dump_path(location_config), 'PostgreSQL', log_prefix, dry_run)
|
||||
|
||||
|
||||
def make_database_dump_pattern(
|
||||
|
|
@ -104,6 +126,9 @@ def restore_database_dump(database_config, log_prefix, location_config, dry_run,
|
|||
Use the given log prefix in any log entries. If this is a dry run, then don't actually restore
|
||||
anything. Trigger the given active extract process (an instance of subprocess.Popen) to produce
|
||||
output to consume.
|
||||
|
||||
If the extract process is None, then restore the dump from the filesystem rather than from an
|
||||
extract stream.
|
||||
'''
|
||||
dry_run_label = ' (dry run; not actually restoring anything)' if dry_run else ''
|
||||
|
||||
|
|
@ -112,6 +137,9 @@ def restore_database_dump(database_config, log_prefix, location_config, dry_run,
|
|||
|
||||
database = database_config[0]
|
||||
all_databases = bool(database['name'] == 'all')
|
||||
dump_filename = dump.make_database_dump_filename(
|
||||
make_dump_path(location_config), database['name'], database.get('hostname')
|
||||
)
|
||||
analyze_command = (
|
||||
('psql', '--no-password', '--quiet')
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
|
|
@ -130,8 +158,9 @@ def restore_database_dump(database_config, log_prefix, location_config, dry_run,
|
|||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--username', database['username']) if 'username' in database else ())
|
||||
+ (() if extract_process else (dump_filename,))
|
||||
)
|
||||
extra_environment = {'PGPASSWORD': database['password']} if 'password' in database else None
|
||||
extra_environment = make_extra_environment(database)
|
||||
|
||||
logger.debug(
|
||||
'{}: Restoring PostgreSQL database {}{}'.format(log_prefix, database['name'], dry_run_label)
|
||||
|
|
@ -141,9 +170,9 @@ def restore_database_dump(database_config, log_prefix, location_config, dry_run,
|
|||
|
||||
execute_command_with_processes(
|
||||
restore_command,
|
||||
[extract_process],
|
||||
[extract_process] if extract_process else [],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
input_file=extract_process.stdout if extract_process else None,
|
||||
extra_environment=extra_environment,
|
||||
borg_local_path=location_config.get('local_path', 'borg'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ pre {
|
|||
padding: .5em;
|
||||
margin: 1em -.5em 2em -.5em;
|
||||
overflow-x: auto;
|
||||
background-color: #eee;
|
||||
background-color: #fafafa;
|
||||
font-size: 0.75em; /* 12px /16 */
|
||||
}
|
||||
pre,
|
||||
|
|
@ -194,7 +194,7 @@ code {
|
|||
-webkit-hyphens: manual;
|
||||
-moz-hyphens: manual;
|
||||
hyphens: manual;
|
||||
background-color: #efefef;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
pre + pre[class*="language-"] {
|
||||
margin-top: 1em;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
/*
|
||||
* Modified with an approximation of the One Light syntax highlighting theme.
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #ABB2BF;
|
||||
color: #494b53;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
|
|
@ -26,13 +29,15 @@ pre[class*="language-"] {
|
|||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #383e49;
|
||||
color: #232324;
|
||||
background: #dbdbdc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #9aa2b1;
|
||||
color: #232324;
|
||||
background: #dbdbdc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
|
@ -50,7 +55,7 @@ pre[class*="language-"] {
|
|||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #282c34;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
|
|
@ -64,16 +69,16 @@ pre[class*="language-"] {
|
|||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #5C6370;
|
||||
color: #505157;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #abb2bf;
|
||||
color: #526fff;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.tag {
|
||||
color: #e06c75;
|
||||
color: none;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
|
|
@ -83,7 +88,7 @@ pre[class*="language-"] {
|
|||
.token.symbol,
|
||||
.token.attr-name,
|
||||
.token.deleted {
|
||||
color: #d19a66;
|
||||
color: #986801;
|
||||
}
|
||||
|
||||
.token.string,
|
||||
|
|
@ -91,7 +96,7 @@ pre[class*="language-"] {
|
|||
.token.attr-value,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #98c379;
|
||||
color: #50a14f;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
|
|
@ -99,22 +104,22 @@ pre[class*="language-"] {
|
|||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #56b6c2;
|
||||
color: #526fff;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.keyword {
|
||||
color: #e06c75;
|
||||
color: #e45649;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #61afef;
|
||||
color: #4078f2;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #c678dd;
|
||||
color: #e45649;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
|
|
|
|||
|
|
@ -24,12 +24,17 @@ hooks:
|
|||
|
||||
As part of each backup, borgmatic streams a database dump for each configured
|
||||
database directly to Borg, so it's included in the backup without consuming
|
||||
additional disk space.
|
||||
additional disk space. (The one exception is PostgreSQL's "directory" dump
|
||||
format, which can't stream and therefore does consume temporary disk space.)
|
||||
|
||||
To support this, borgmatic creates temporary named pipes in `~/.borgmatic` by
|
||||
default. To customize this path, set the `borgmatic_source_directory` option
|
||||
in the `location` section of borgmatic's configuration.
|
||||
|
||||
Also note that using a database hook implicitly enables both the
|
||||
`read_special` and `one_file_system` configuration settings (even if they're
|
||||
disabled in your configuration) to support this dump and restore streaming.
|
||||
|
||||
Here's a more involved example that connects to remote databases:
|
||||
|
||||
```yaml
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ below for how to configure this.
|
|||
software to consume borgmatic JSON output and track when the last
|
||||
successful backup occurred. See [scripting
|
||||
borgmatic](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#scripting-borgmatic)
|
||||
and [related software](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#related-software)
|
||||
below for how to configure this.
|
||||
5. **Borg hosting providers**: Most [Borg hosting
|
||||
providers](https://torsion.org/borgmatic/#hosting-providers) include
|
||||
|
|
@ -116,7 +117,7 @@ hooks:
|
|||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Healthchecks project when a
|
||||
backup begins, ends, or errors. Specifically, before the <a
|
||||
backup begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Healthchecks know that it has started if any of
|
||||
the `prune`, `create`, or `check` actions are run.
|
||||
|
|
@ -127,10 +128,10 @@ the payload data sent to Healthchecks. This means that borgmatic logs show up
|
|||
in the Healthchecks UI, although be aware that Healthchecks currently has a
|
||||
10-kilobyte limit for the logs in each ping.
|
||||
|
||||
If an error occurs during any action, borgmatic notifies Healthchecks after
|
||||
the `on_error` hooks run, also tacking on logs including the error itself. But
|
||||
the logs are only included for errors that occur when a `prune`, `create`, or
|
||||
`check` action is run.
|
||||
If an error occurs during any action or hook, borgmatic notifies Healthchecks
|
||||
after the `on_error` hooks run, also tacking on logs including the error
|
||||
itself. But the logs are only included for errors that occur when a `prune`,
|
||||
`create`, or `check` action is run.
|
||||
|
||||
You can customize the verbosity of the logs that are sent to Healthchecks with
|
||||
borgmatic's `--monitoring-verbosity` flag. The `--files` and `--stats` flags
|
||||
|
|
@ -156,13 +157,13 @@ hooks:
|
|||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Cronitor monitor when a backup
|
||||
begins, ends, or errors. Specifically, before the <a
|
||||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronitor know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronitor of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action, borgmatic
|
||||
notifies Cronitor after the `on_error` hooks run.
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronitor after the `on_error` hooks run.
|
||||
|
||||
You can configure Cronitor to notify you by a [variety of
|
||||
mechanisms](https://cronitor.io/docs/cron-job-notifications) when backups fail
|
||||
|
|
@ -184,13 +185,13 @@ hooks:
|
|||
```
|
||||
|
||||
With this hook in place, borgmatic pings your Cronhub monitor when a backup
|
||||
begins, ends, or errors. Specifically, before the <a
|
||||
begins, ends, or errors. Specifically, after the <a
|
||||
href="https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/">`before_backup`
|
||||
hooks</a> run, borgmatic lets Cronhub know that it has started if any of the
|
||||
`prune`, `create`, or `check` actions are run. Then, if the actions complete
|
||||
successfully, borgmatic notifies Cronhub of the success after the
|
||||
`after_backup` hooks run. And if an error occurs during any action, borgmatic
|
||||
notifies Cronhub after the `on_error` hooks run.
|
||||
`after_backup` hooks run. And if an error occurs during any action or hook,
|
||||
borgmatic notifies Cronhub after the `on_error` hooks run.
|
||||
|
||||
Note that even though you configure borgmatic with the "start" variant of the
|
||||
ping URL, borgmatic substitutes the correct state into the URL when pinging
|
||||
|
|
@ -227,7 +228,7 @@ hooks:
|
|||
|
||||
With this hook in place, borgmatic creates a PagerDuty event for your service
|
||||
whenever backups fail. Specifically, if an error occurs during a `create`,
|
||||
`prune`, or `check` action, borgmatic sends an event to PagerDuty after the
|
||||
`prune`, or `check` action, borgmatic sends an event to PagerDuty before the
|
||||
`on_error` hooks run. Note that borgmatic does not contact PagerDuty when a
|
||||
backup starts or ends without error.
|
||||
|
||||
|
|
@ -250,6 +251,11 @@ suppressed so as not to interfere with the captured JSON. Also note that JSON
|
|||
output only shows up at the console, and not in syslog.
|
||||
|
||||
|
||||
## Related software
|
||||
|
||||
* [Borgmacator GNOME AppIndicator](https://github.com/N-Coder/borgmacator/)
|
||||
|
||||
|
||||
### Successful backups
|
||||
|
||||
`borgmatic list` includes support for a `--successful` flag that only lists
|
||||
|
|
|
|||
|
|
@ -8,34 +8,71 @@ these instructions install and run borgmatic as root. If you don't need to
|
|||
backup such files, then you are welcome to install and run borgmatic as a
|
||||
non-root user.
|
||||
|
||||
First, [install
|
||||
First, manually [install
|
||||
Borg](https://borgbackup.readthedocs.io/en/stable/installation.html), at least
|
||||
version 1.1.
|
||||
version 1.1. borgmatic does not install Borg automatically so as to avoid
|
||||
conflicts with existing Borg installations.
|
||||
|
||||
Then, download and install borgmatic by running the following command:
|
||||
Then, download and install borgmatic as a [user site
|
||||
installation](https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site)
|
||||
by running the following command:
|
||||
|
||||
```bash
|
||||
sudo pip3 install --user --upgrade borgmatic
|
||||
```
|
||||
|
||||
This is a [recommended user site
|
||||
installation](https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site).
|
||||
You will need to ensure that `/root/.local/bin` is available on your `$PATH`
|
||||
so
|
||||
that the borgmatic executable is available. For instance, adding this to
|
||||
root's `~/.profile` or `~/.bash_profile` may do the trick:
|
||||
This installs borgmatic and its commands at the `/root/.local/bin` path.
|
||||
|
||||
Your pip binary may have a different name than "pip3". Make sure you're using
|
||||
Python 3, as borgmatic does not support Python 2.
|
||||
|
||||
The next step is to ensure that borgmatic's commands available are on your
|
||||
system `PATH`, so that you can run borgmatic:
|
||||
|
||||
```bash
|
||||
export PATH="$PATH:~/.local/bin"
|
||||
echo export 'PATH="$PATH:/root/.local/bin"' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
Note that your pip binary may have a different name than "pip3". Make sure
|
||||
you're using Python 3, as borgmatic does not support Python 2.
|
||||
This adds `/root/.local/bin` to your non-root user's system `PATH`.
|
||||
|
||||
If you're using a command shell other than Bash, you may need to use different
|
||||
commands here.
|
||||
|
||||
You can check whether all of this worked with:
|
||||
|
||||
```bash
|
||||
sudo borgmatic --version
|
||||
```
|
||||
|
||||
If borgmatic is properly installed, that should output your borgmatic version.
|
||||
|
||||
|
||||
### Global install option
|
||||
|
||||
If you try the user site installation above, and have problems making
|
||||
borgmatic commands runnable on your system `PATH`, an alternate approach is to
|
||||
install borgmatic globally.
|
||||
|
||||
The following uninstalls borgmatic, and then reinstalls it such that borgmatic
|
||||
commands are on the default system `PATH`:
|
||||
|
||||
```bash
|
||||
sudo pip3 uninstall borgmatic
|
||||
sudo pip3 install --upgrade borgmatic
|
||||
```
|
||||
|
||||
The main downside of a global install is that borgmatic is less cleanly
|
||||
separated from the rest of your Python software, and there's the theoretical
|
||||
possibility of libary conflicts. But if you're okay with that, for instance
|
||||
on a relatively dedicated system, then a global install can work out just
|
||||
fine.
|
||||
|
||||
|
||||
### Other ways to install
|
||||
|
||||
Along with the above process, you have several other options for installing
|
||||
borgmatic:
|
||||
Besides the approaches described above, there are several other options for
|
||||
installing borgmatic:
|
||||
|
||||
* [Docker image with scheduled backups](https://hub.docker.com/r/b3vis/borgmatic/)
|
||||
* [Docker base image](https://hub.docker.com/r/monachus/borgmatic/)
|
||||
|
|
@ -170,16 +207,20 @@ good idea to test that borgmatic is working. So to run borgmatic and start a
|
|||
backup, you can invoke it like this:
|
||||
|
||||
```bash
|
||||
sudo borgmatic --verbosity 1
|
||||
sudo borgmatic --verbosity 1 --files
|
||||
```
|
||||
|
||||
(No borgmatic `--files` flag? It's only present in newer versions of
|
||||
borgmatic. So try leaving it out, or upgrade borgmatic!)
|
||||
|
||||
By default, this will also prune any old backups as per the configured
|
||||
retention policy, and check backups for consistency problems due to things
|
||||
like file damage.
|
||||
|
||||
The verbosity flag makes borgmatic list the files that it's archiving, which
|
||||
are those that are new or changed since the last backup. Eyeball the list and
|
||||
see if it matches your expectations based on the configuration.
|
||||
The verbosity flag makes borgmatic show the steps it's performing. And the
|
||||
files flag lists each file that's new or changed since the last backup.
|
||||
Eyeball the list and see if it matches your expectations based on the
|
||||
configuration.
|
||||
|
||||
If you'd like to specify an alternate configuration file path, use the
|
||||
`--config` flag. See `borgmatic --help` for more information.
|
||||
|
|
@ -228,6 +269,7 @@ issues when reading files to backup. If that happens to you, you may be
|
|||
interested in an [unofficial work-around for Full Disk
|
||||
Access](https://projects.torsion.org/witten/borgmatic/issues/293).
|
||||
|
||||
|
||||
## Colored output
|
||||
|
||||
Borgmatic produces colored terminal output by default. It is disabled when a
|
||||
|
|
@ -236,6 +278,7 @@ non-interactive terminal is detected (like a cron job), or when you use the
|
|||
setting the environment variable `PY_COLORS=False`, or setting the `color`
|
||||
option to `false` in the `output` section of configuration.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "found character that cannot start any token" error
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Restart=no
|
|||
# doesn't support this (pre-240 or so), you may have to remove this option.
|
||||
LogRateLimitIntervalSec=0
|
||||
|
||||
# Delay start to prevent backups running during boot.
|
||||
# Delay start to prevent backups running during boot. Note that systemd-inhibit requires dbus and
|
||||
# dbus-user-session to be installed.
|
||||
ExecStartPre=sleep 1m
|
||||
ExecStart=systemd-inhibit --who="borgmatic" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --syslog-verbosity 1
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -1,6 +1,6 @@
|
|||
from setuptools import find_packages, setup
|
||||
|
||||
VERSION = '1.5.4'
|
||||
VERSION = '1.5.7.dev0'
|
||||
|
||||
|
||||
setup(
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import tempfile
|
|||
import pytest
|
||||
|
||||
|
||||
def write_configuration(config_path, repository_path, borgmatic_source_directory):
|
||||
def write_configuration(
|
||||
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format='custom'
|
||||
):
|
||||
'''
|
||||
Write out borgmatic configuration into a file at the config path. Set the options so as to work
|
||||
for testing. This includes injecting the given repository path, borgmatic source directory for
|
||||
storing database dumps, and encryption passphrase.
|
||||
storing database dumps, dump format (for PostgreSQL), and encryption passphrase.
|
||||
'''
|
||||
config = '''
|
||||
location:
|
||||
|
|
@ -31,6 +33,7 @@ hooks:
|
|||
hostname: postgresql
|
||||
username: postgres
|
||||
password: test
|
||||
format: {}
|
||||
- name: all
|
||||
hostname: postgresql
|
||||
username: postgres
|
||||
|
|
@ -45,7 +48,7 @@ hooks:
|
|||
username: root
|
||||
password: test
|
||||
'''.format(
|
||||
config_path, repository_path, borgmatic_source_directory
|
||||
config_path, repository_path, borgmatic_source_directory, postgresql_dump_format
|
||||
)
|
||||
|
||||
config_file = open(config_path, 'w')
|
||||
|
|
@ -93,6 +96,39 @@ def test_database_dump_and_restore():
|
|||
shutil.rmtree(temporary_directory)
|
||||
|
||||
|
||||
def test_database_dump_and_restore_with_directory_format():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
borgmatic_source_directory = os.path.join(temporary_directory, '.borgmatic')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
write_configuration(
|
||||
config_path,
|
||||
repository_path,
|
||||
borgmatic_source_directory,
|
||||
postgresql_dump_format='directory',
|
||||
)
|
||||
|
||||
subprocess.check_call(
|
||||
'borgmatic -v 2 --config {} init --encryption repokey'.format(config_path).split(' ')
|
||||
)
|
||||
|
||||
# Run borgmatic to generate a backup archive including a database dump.
|
||||
subprocess.check_call('borgmatic create --config {} -v 2'.format(config_path).split(' '))
|
||||
|
||||
# Restore the database from the archive.
|
||||
subprocess.check_call(
|
||||
'borgmatic --config {} restore --archive latest'.format(config_path).split(' ')
|
||||
)
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
||||
|
||||
|
||||
def test_database_dump_with_error_causes_borgmatic_to_exit():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
|
|
|
|||
8
tests/integration/config/test_schema.py
Normal file
8
tests/integration/config/test_schema.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
MAXIMUM_LINE_LENGTH = 80
|
||||
|
||||
|
||||
def test_schema_line_length_stays_under_limit():
|
||||
schema_file = open('borgmatic/config/schema.yaml')
|
||||
|
||||
for line in schema_file.readlines():
|
||||
assert len(line.rstrip('\n')) <= MAXIMUM_LINE_LENGTH
|
||||
24
tests/integration/hooks/test_healthchecks.py
Normal file
24
tests/integration/hooks/test_healthchecks.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import logging
|
||||
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks import healthchecks as module
|
||||
|
||||
|
||||
def test_destroy_monitor_removes_healthchecks_handler():
|
||||
logger = logging.getLogger()
|
||||
original_handlers = list(logger.handlers)
|
||||
logger.addHandler(module.Forgetful_buffering_handler(byte_capacity=100, log_level=1))
|
||||
|
||||
module.destroy_monitor(flexmock(), flexmock(), flexmock(), flexmock())
|
||||
|
||||
assert logger.handlers == original_handlers
|
||||
|
||||
|
||||
def test_destroy_monitor_without_healthchecks_handler_does_not_raise():
|
||||
logger = logging.getLogger()
|
||||
original_handlers = list(logger.handlers)
|
||||
|
||||
module.destroy_monitor(flexmock(), flexmock(), flexmock(), flexmock())
|
||||
|
||||
assert logger.handlers == original_handlers
|
||||
|
|
@ -66,7 +66,6 @@ def test_log_outputs_includes_error_output_in_exception():
|
|||
(process,), exclude_stdouts=(), output_log_level=logging.INFO, borg_local_path='borg'
|
||||
)
|
||||
|
||||
assert error.value.returncode == 2
|
||||
assert error.value.output
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,31 @@ def test_expand_home_directories_considers_none_as_no_directories():
|
|||
assert paths == ()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'directories,expected_directories',
|
||||
(
|
||||
({'/': 1, '/root': 1}, ('/',)),
|
||||
({'/': 1, '/root/': 1}, ('/',)),
|
||||
({'/': 1, '/root': 2}, ('/', '/root')),
|
||||
({'/root': 1, '/': 1}, ('/',)),
|
||||
({'/root': 1, '/root/foo': 1}, ('/root',)),
|
||||
({'/root/': 1, '/root/foo': 1}, ('/root/',)),
|
||||
({'/root': 1, '/root/foo/': 1}, ('/root',)),
|
||||
({'/root': 1, '/root/foo': 2}, ('/root', '/root/foo')),
|
||||
({'/root/foo': 1, '/root': 1}, ('/root',)),
|
||||
({'/root': 1, '/etc': 1, '/root/foo/bar': 1}, ('/etc', '/root')),
|
||||
({'/root': 1, '/root/foo': 1, '/root/foo/bar': 1}, ('/root',)),
|
||||
({'/dup': 1, '/dup': 1}, ('/dup',)),
|
||||
({'/foo': 1, '/bar': 1}, ('/bar', '/foo')),
|
||||
({'/foo': 1, '/bar': 2}, ('/bar', '/foo')),
|
||||
),
|
||||
)
|
||||
def test_deduplicate_directories_removes_child_paths_on_the_same_filesystem(
|
||||
directories, expected_directories
|
||||
):
|
||||
assert module.deduplicate_directories(directories) == expected_directories
|
||||
|
||||
|
||||
def test_write_pattern_file_does_not_raise():
|
||||
temporary_file = flexmock(name='filename', write=lambda mode: None, flush=lambda: None)
|
||||
flexmock(module.tempfile).should_receive('NamedTemporaryFile').and_return(temporary_file)
|
||||
|
|
@ -214,7 +239,9 @@ ARCHIVE_WITH_PATHS = ('repo::{}'.format(DEFAULT_ARCHIVE_NAME), 'foo', 'bar')
|
|||
|
||||
def test_create_archive_calls_borg_with_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -241,7 +268,9 @@ def test_create_archive_calls_borg_with_parameters():
|
|||
def test_create_archive_with_patterns_calls_borg_with_patterns():
|
||||
pattern_flags = ('--patterns-from', 'patterns')
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(
|
||||
flexmock(name='/tmp/patterns')
|
||||
|
|
@ -270,7 +299,9 @@ def test_create_archive_with_patterns_calls_borg_with_patterns():
|
|||
def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
|
||||
exclude_flags = ('--exclude-from', 'excludes')
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(('exclude',))
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None).and_return(
|
||||
flexmock(name='/tmp/excludes')
|
||||
|
|
@ -298,7 +329,9 @@ def test_create_archive_with_exclude_patterns_calls_borg_with_excludes():
|
|||
|
||||
def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -326,7 +359,9 @@ def test_create_archive_with_log_info_calls_borg_with_info_parameter():
|
|||
|
||||
def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -355,7 +390,9 @@ def test_create_archive_with_log_info_and_json_suppresses_most_borg_output():
|
|||
|
||||
def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -382,7 +419,9 @@ def test_create_archive_with_log_debug_calls_borg_with_debug_parameter():
|
|||
|
||||
def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -410,7 +449,9 @@ def test_create_archive_with_log_debug_and_json_suppresses_most_borg_output():
|
|||
|
||||
def test_create_archive_with_dry_run_calls_borg_with_dry_run_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -439,7 +480,9 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
|
|||
# --dry-run and --stats are mutually exclusive, see:
|
||||
# https://borgbackup.readthedocs.io/en/stable/usage/create.html#description
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -468,7 +511,9 @@ def test_create_archive_with_stats_and_dry_run_calls_borg_without_stats_paramete
|
|||
|
||||
def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_interval_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -494,7 +539,9 @@ def test_create_archive_with_checkpoint_interval_calls_borg_with_checkpoint_inte
|
|||
|
||||
def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -520,7 +567,9 @@ def test_create_archive_with_chunker_params_calls_borg_with_chunker_params_param
|
|||
|
||||
def test_create_archive_with_compression_calls_borg_with_compression_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -546,7 +595,9 @@ def test_create_archive_with_compression_calls_borg_with_compression_parameters(
|
|||
|
||||
def test_create_archive_with_remote_rate_limit_calls_borg_with_remote_ratelimit_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -572,7 +623,9 @@ def test_create_archive_with_remote_rate_limit_calls_borg_with_remote_ratelimit_
|
|||
|
||||
def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -599,7 +652,9 @@ def test_create_archive_with_one_file_system_calls_borg_with_one_file_system_par
|
|||
|
||||
def test_create_archive_with_numeric_owner_calls_borg_with_numeric_owner_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -626,7 +681,9 @@ def test_create_archive_with_numeric_owner_calls_borg_with_numeric_owner_paramet
|
|||
|
||||
def test_create_archive_with_read_special_calls_borg_with_read_special_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -654,7 +711,9 @@ def test_create_archive_with_read_special_calls_borg_with_read_special_parameter
|
|||
@pytest.mark.parametrize('option_name', ('atime', 'ctime', 'birthtime', 'bsd_flags'))
|
||||
def test_create_archive_with_option_true_calls_borg_without_corresponding_parameter(option_name):
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -682,7 +741,9 @@ def test_create_archive_with_option_true_calls_borg_without_corresponding_parame
|
|||
@pytest.mark.parametrize('option_name', ('atime', 'ctime', 'birthtime', 'bsd_flags'))
|
||||
def test_create_archive_with_option_false_calls_borg_with_corresponding_parameter(option_name):
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -709,7 +770,9 @@ def test_create_archive_with_option_false_calls_borg_with_corresponding_paramete
|
|||
|
||||
def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -736,7 +799,9 @@ def test_create_archive_with_files_cache_calls_borg_with_files_cache_parameters(
|
|||
|
||||
def test_create_archive_with_local_path_calls_borg_via_local_path():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -763,7 +828,9 @@ def test_create_archive_with_local_path_calls_borg_via_local_path():
|
|||
|
||||
def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -790,7 +857,9 @@ def test_create_archive_with_remote_path_calls_borg_with_remote_path_parameters(
|
|||
|
||||
def test_create_archive_with_umask_calls_borg_with_umask_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -816,7 +885,9 @@ def test_create_archive_with_umask_calls_borg_with_umask_parameters():
|
|||
|
||||
def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -842,7 +913,9 @@ def test_create_archive_with_lock_wait_calls_borg_with_lock_wait_parameters():
|
|||
|
||||
def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_output_log_level():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -869,7 +942,9 @@ def test_create_archive_with_stats_calls_borg_with_stats_parameter_and_warning_o
|
|||
|
||||
def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_and_info_output_log_level():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -897,7 +972,9 @@ def test_create_archive_with_stats_and_log_info_calls_borg_with_stats_parameter_
|
|||
|
||||
def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_output_log_level():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -924,7 +1001,9 @@ def test_create_archive_with_files_calls_borg_with_list_parameter_and_warning_ou
|
|||
|
||||
def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_and_info_output_log_level():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -952,7 +1031,9 @@ def test_create_archive_with_files_and_log_info_calls_borg_with_list_parameter_a
|
|||
|
||||
def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_parameter_and_no_list():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -980,7 +1061,9 @@ def test_create_archive_with_progress_and_log_info_calls_borg_with_progress_para
|
|||
|
||||
def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1008,13 +1091,16 @@ def test_create_archive_with_progress_calls_borg_with_progress_parameter():
|
|||
def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progress_parameter():
|
||||
processes = flexmock()
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
flexmock(module).should_receive('_make_exclude_flags').and_return(())
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('borg', 'create', '--read-special', '--progress') + ARCHIVE_WITH_PATHS,
|
||||
('borg', 'create', '--one-file-system', '--read-special', '--progress')
|
||||
+ ARCHIVE_WITH_PATHS,
|
||||
processes=processes,
|
||||
output_log_level=logging.INFO,
|
||||
output_file=module.DO_NOT_CAPTURE,
|
||||
|
|
@ -1037,7 +1123,9 @@ def test_create_archive_with_progress_and_stream_processes_calls_borg_with_progr
|
|||
|
||||
def test_create_archive_with_json_calls_borg_with_json_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1066,7 +1154,9 @@ def test_create_archive_with_json_calls_borg_with_json_parameter():
|
|||
|
||||
def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1096,7 +1186,9 @@ def test_create_archive_with_stats_and_json_calls_borg_without_stats_parameter()
|
|||
|
||||
def test_create_archive_with_source_directories_glob_expands():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'food'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1123,7 +1215,9 @@ def test_create_archive_with_source_directories_glob_expands():
|
|||
|
||||
def test_create_archive_with_non_matching_source_directories_glob_passes_through():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo*',))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo*',))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1150,7 +1244,9 @@ def test_create_archive_with_non_matching_source_directories_glob_passes_through
|
|||
|
||||
def test_create_archive_with_glob_calls_borg_with_expanded_directories():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'food'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'food'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1176,7 +1272,9 @@ def test_create_archive_with_glob_calls_borg_with_expanded_directories():
|
|||
|
||||
def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1202,7 +1300,9 @@ def test_create_archive_with_archive_name_format_calls_borg_with_archive_name():
|
|||
|
||||
def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1228,7 +1328,9 @@ def test_create_archive_with_archive_name_format_accepts_borg_placeholders():
|
|||
|
||||
def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
|
|
@ -1255,13 +1357,15 @@ def test_create_archive_with_extra_borg_options_calls_borg_with_extra_options():
|
|||
def test_create_archive_with_stream_processes_calls_borg_with_processes():
|
||||
processes = flexmock()
|
||||
flexmock(module).should_receive('borgmatic_source_directories').and_return([])
|
||||
flexmock(module).should_receive('_expand_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('deduplicate_directories').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('map_directories_to_devices').and_return({})
|
||||
flexmock(module).should_receive('_expand_directories').and_return(())
|
||||
flexmock(module).should_receive('_expand_home_directories').and_return(())
|
||||
flexmock(module).should_receive('_write_pattern_file').and_return(None)
|
||||
flexmock(module).should_receive('_make_pattern_flags').and_return(())
|
||||
flexmock(module).should_receive('_make_exclude_flags').and_return(())
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('borg', 'create', '--read-special') + ARCHIVE_WITH_PATHS,
|
||||
('borg', 'create', '--one-file-system', '--read-special') + ARCHIVE_WITH_PATHS,
|
||||
processes=processes,
|
||||
output_log_level=logging.INFO,
|
||||
output_file=None,
|
||||
|
|
|
|||
|
|
@ -60,3 +60,25 @@ def test_initialize_with_relocated_repo_access_should_override_default():
|
|||
assert os.environ.get('BORG_RELOCATED_REPO_ACCESS_IS_OK') == 'yes'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
||||
|
||||
def test_initialize_prefers_configuration_option_over_borg_environment_variable():
|
||||
orig_environ = os.environ
|
||||
|
||||
try:
|
||||
os.environ = {'BORG_SSH': 'mosh'}
|
||||
module.initialize({'ssh_command': 'ssh -C'})
|
||||
assert os.environ.get('BORG_RSH') == 'ssh -C'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
||||
|
||||
def test_initialize_passes_through_existing_borg_environment_variable():
|
||||
orig_environ = os.environ
|
||||
|
||||
try:
|
||||
os.environ = {'BORG_PASSPHRASE': 'pass'}
|
||||
module.initialize({'ssh_command': 'ssh -C'})
|
||||
assert os.environ.get('BORG_PASSPHRASE') == 'pass'
|
||||
finally:
|
||||
os.environ = orig_environ
|
||||
|
|
|
|||
|
|
@ -220,6 +220,21 @@ def test_extract_archive_calls_borg_with_destination_path():
|
|||
)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_strip_components():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
insert_execute_command_mock(('borg', 'extract', '--strip-components', '5', 'repo::archive'))
|
||||
|
||||
module.extract_archive(
|
||||
dry_run=False,
|
||||
repository='repo',
|
||||
archive='archive',
|
||||
paths=None,
|
||||
location_config={},
|
||||
storage_config={},
|
||||
strip_components=5,
|
||||
)
|
||||
|
||||
|
||||
def test_extract_archive_calls_borg_with_progress_parameter():
|
||||
flexmock(module.os.path).should_receive('abspath').and_return('repo')
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
|
|
|
|||
|
|
@ -34,61 +34,41 @@ def test_make_database_dump_filename_with_invalid_name_raises():
|
|||
module.make_database_dump_filename('databases', 'invalid/name')
|
||||
|
||||
|
||||
def test_create_named_pipe_for_dump_does_not_raise():
|
||||
def test_create_parent_directory_for_dump_does_not_raise():
|
||||
flexmock(module.os).should_receive('makedirs')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.os).should_receive('remove')
|
||||
|
||||
module.create_parent_directory_for_dump('/path/to/parent')
|
||||
|
||||
|
||||
def test_create_named_pipe_for_dump_does_not_raise():
|
||||
flexmock(module).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.os).should_receive('mkfifo')
|
||||
|
||||
module.create_named_pipe_for_dump('/path/to/pipe')
|
||||
|
||||
|
||||
def test_remove_database_dumps_removes_dump_for_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'foo', None
|
||||
).and_return('databases/localhost/foo')
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'bar', None
|
||||
).and_return('databases/localhost/bar')
|
||||
def test_remove_database_dumps_removes_dump_path():
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.shutil).should_receive('rmtree').with_args('databases/localhost').once()
|
||||
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(False)
|
||||
flexmock(module.os).should_receive('remove').with_args('databases/localhost/foo').once()
|
||||
flexmock(module.os).should_receive('remove').with_args('databases/localhost/bar').once()
|
||||
flexmock(module.os).should_receive('listdir').with_args('databases/localhost').and_return(
|
||||
['bar']
|
||||
).and_return([])
|
||||
|
||||
flexmock(module.os).should_receive('rmdir').with_args('databases/localhost').once()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_remove_database_dumps_removes_dump_in_directory_format():
|
||||
databases = [{'name': 'foo'}]
|
||||
flexmock(module).should_receive('make_database_dump_filename').with_args(
|
||||
'databases', 'foo', None
|
||||
).and_return('databases/localhost/foo')
|
||||
|
||||
flexmock(module.os.path).should_receive('isdir').and_return(True)
|
||||
flexmock(module.os).should_receive('remove').never()
|
||||
flexmock(module.shutil).should_receive('rmtree').with_args('databases/localhost/foo').once()
|
||||
flexmock(module.os).should_receive('listdir').with_args('databases/localhost').and_return([])
|
||||
flexmock(module.os).should_receive('rmdir').with_args('databases/localhost').once()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=False)
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_remove_database_dumps_with_dry_run_skips_removal():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
flexmock(module.os).should_receive('rmdir').never()
|
||||
flexmock(module.os).should_receive('remove').never()
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').never()
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
|
||||
module.remove_database_dumps('databases', databases, 'SuperDB', 'test.yaml', dry_run=True)
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=True)
|
||||
|
||||
|
||||
def test_remove_database_dumps_without_databases_does_not_raise():
|
||||
module.remove_database_dumps('databases', [], 'SuperDB', 'test.yaml', dry_run=False)
|
||||
def test_remove_database_dumps_without_dump_path_present_skips_removal():
|
||||
flexmock(module.os.path).should_receive('expanduser').and_return('databases/localhost')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.shutil).should_receive('rmtree').never()
|
||||
|
||||
module.remove_database_dumps('databases', 'SuperDB', 'test.yaml', dry_run=False)
|
||||
|
||||
|
||||
def test_convert_glob_patterns_to_borg_patterns_removes_leading_slash():
|
||||
|
|
|
|||
|
|
@ -198,6 +198,19 @@ def test_dump_databases_runs_mysqldump_for_all_databases():
|
|||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
|
||||
|
||||
|
||||
def test_dump_databases_errors_for_missing_all_databases():
|
||||
databases = [{'name': 'all'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(())
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
|
||||
|
||||
|
||||
def test_restore_database_dump_runs_mysql_to_restore():
|
||||
database_config = [{'name': 'foo'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ def test_dump_databases_runs_pg_dump_for_each_database():
|
|||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
|
|
@ -29,7 +30,7 @@ def test_dump_databases_runs_pg_dump_for_each_database():
|
|||
'databases/localhost/{}'.format(name),
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ def test_dump_databases_with_dry_run_skips_pg_dump():
|
|||
'databases/localhost/foo'
|
||||
).and_return('databases/localhost/bar')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=True) == []
|
||||
|
|
@ -56,6 +58,7 @@ def test_dump_databases_runs_pg_dump_with_hostname_and_port():
|
|||
'databases/database.example.org/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
|
|
@ -74,7 +77,7 @@ def test_dump_databases_runs_pg_dump_with_hostname_and_port():
|
|||
'databases/database.example.org/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
|
|
@ -89,6 +92,9 @@ def test_dump_databases_runs_pg_dump_with_username_and_password():
|
|||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
|
|
@ -105,21 +111,46 @@ def test_dump_databases_runs_pg_dump_with_username_and_password():
|
|||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert module.dump_databases(databases, 'test.yaml', {}, dry_run=False) == [process]
|
||||
|
||||
|
||||
def test_dump_databases_runs_pg_dump_with_format():
|
||||
databases = [{'name': 'foo', 'format': 'tar'}]
|
||||
def test_make_extra_environment_maps_options_to_environment():
|
||||
database = {
|
||||
'name': 'foo',
|
||||
'password': 'pass',
|
||||
'ssl_mode': 'require',
|
||||
'ssl_cert': 'cert.crt',
|
||||
'ssl_key': 'key.key',
|
||||
'ssl_root_cert': 'root.crt',
|
||||
'ssl_crl': 'crl.crl',
|
||||
}
|
||||
expected = {
|
||||
'PGPASSWORD': 'pass',
|
||||
'PGSSLMODE': 'require',
|
||||
'PGSSLCERT': 'cert.crt',
|
||||
'PGSSLKEY': 'key.key',
|
||||
'PGSSLROOTCERT': 'root.crt',
|
||||
'PGSSLCRL': 'crl.crl',
|
||||
}
|
||||
|
||||
extra_env = module.make_extra_environment(database)
|
||||
assert extra_env == expected
|
||||
|
||||
|
||||
def test_dump_databases_runs_pg_dump_with_directory_format():
|
||||
databases = [{'name': 'foo', 'format': 'directory'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename').and_return(
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module.dump).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
|
|
@ -128,13 +159,13 @@ def test_dump_databases_runs_pg_dump_with_format():
|
|||
'--clean',
|
||||
'--if-exists',
|
||||
'--format',
|
||||
'tar',
|
||||
'foo',
|
||||
'>',
|
||||
'directory',
|
||||
'--file',
|
||||
'databases/localhost/foo',
|
||||
'foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
|
|
@ -149,6 +180,7 @@ def test_dump_databases_runs_pg_dump_with_options():
|
|||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
|
|
@ -164,7 +196,7 @@ def test_dump_databases_runs_pg_dump_with_options():
|
|||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
|
|
@ -179,11 +211,12 @@ def test_dump_databases_runs_pg_dumpall_for_all_databases():
|
|||
'databases/localhost/all'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('pg_dumpall', '--no-password', '--clean', '--if-exists', '>', 'databases/localhost/all'),
|
||||
shell=True,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
|
|
@ -194,6 +227,9 @@ def test_restore_database_dump_runs_pg_restore():
|
|||
database_config = [{'name': 'foo'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
|
|
@ -207,12 +243,12 @@ def test_restore_database_dump_runs_pg_restore():
|
|||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--dbname', 'foo', '--command', 'ANALYZE'),
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
|
|
@ -223,6 +259,9 @@ def test_restore_database_dump_runs_pg_restore():
|
|||
def test_restore_database_dump_errors_on_multiple_database_config():
|
||||
database_config = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
|
|
@ -236,6 +275,9 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
|||
database_config = [{'name': 'foo', 'hostname': 'database.example.org', 'port': 5433}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
|
|
@ -253,7 +295,7 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
|||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
|
|
@ -270,7 +312,7 @@ def test_restore_database_dump_runs_pg_restore_with_hostname_and_port():
|
|||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
|
|
@ -282,6 +324,11 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
|||
database_config = [{'name': 'foo', 'username': 'postgres', 'password': 'trustsome1'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
|
|
@ -297,7 +344,7 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
|||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
|
|
@ -312,7 +359,7 @@ def test_restore_database_dump_runs_pg_restore_with_username_and_password():
|
|||
'--command',
|
||||
'ANALYZE',
|
||||
),
|
||||
extra_environment={'PGPASSWORD': 'trustsome1'},
|
||||
extra_environment={'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
|
|
@ -324,16 +371,20 @@ def test_restore_database_dump_runs_psql_for_all_database_dump():
|
|||
database_config = [{'name': 'all'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('psql', '--no-password'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
extra_environment=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--command', 'ANALYZE'), extra_environment=None
|
||||
('psql', '--no-password', '--quiet', '--command', 'ANALYZE'),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
|
|
@ -344,8 +395,44 @@ def test_restore_database_dump_runs_psql_for_all_database_dump():
|
|||
def test_restore_database_dump_with_dry_run_skips_restore():
|
||||
database_config = [{'name': 'foo'}]
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_database_dump(
|
||||
database_config, 'test.yaml', {}, dry_run=True, extract_process=flexmock()
|
||||
)
|
||||
|
||||
|
||||
def test_restore_database_dump_without_extract_process_restores_from_disk():
|
||||
database_config = [{'name': 'foo'}]
|
||||
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_database_dump_filename').and_return('/dump/path')
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'pg_restore',
|
||||
'--no-password',
|
||||
'--if-exists',
|
||||
'--exit-on-error',
|
||||
'--clean',
|
||||
'--dbname',
|
||||
'foo',
|
||||
'/dump/path',
|
||||
),
|
||||
processes=[],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=None,
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
borg_local_path='borg',
|
||||
).once()
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
('psql', '--no-password', '--quiet', '--dbname', 'foo', '--command', 'ANALYZE'),
|
||||
extra_environment={'PGSSLMODE': 'disable'},
|
||||
).once()
|
||||
|
||||
module.restore_database_dump(
|
||||
database_config, 'test.yaml', {}, dry_run=False, extract_process=None
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue