mirror of
https://github.com/borgmatic-collective/borgmatic.git
synced 2026-07-22 02:03:01 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe82ff11e | ||
|
|
55c0ab1610 | ||
|
|
1f86100f26 | ||
|
|
2a16ffab1b | ||
|
|
4b2f7e03af | ||
|
|
024006f4c0 | ||
|
|
4c71e600ca | ||
|
|
114f5702b2 | ||
|
|
54afe87a9f | ||
|
|
25b6a49df7 | ||
|
|
b97372adf2 | ||
|
|
6bc9a592d9 | ||
|
|
839862cff0 | ||
|
|
06b065cb09 | ||
|
|
1e5c256d54 | ||
|
|
baf5fec78d | ||
|
|
48a4fbaa89 | ||
|
|
1e274d7153 | ||
|
|
c41b743819 | ||
|
|
36d0073375 | ||
|
|
0bd418836e | ||
|
|
923fa7d82f | ||
|
|
dce0528057 | ||
|
|
8a6c6c84d2 | ||
|
|
1e21c8f97b | ||
|
|
2eab74a521 | ||
|
|
3bca686707 | ||
|
|
8854b9ad20 | ||
|
|
bcc463688a |
@@ -1,3 +1,16 @@
|
||||
1.9.13
|
||||
* #975: Add a "compression" option to the PostgreSQL database hook.
|
||||
* #1001: Fix a ZFS error during snapshot cleanup.
|
||||
* #1003: In the Zabbix monitoring hook, support Zabbix 7.2's authentication changes.
|
||||
* #1009: Send database passwords to MariaDB and MySQL via anonymous pipe, which is more secure than
|
||||
using an environment variable.
|
||||
* #1013: Send database passwords to MongoDB via anonymous pipe, which is more secure than using
|
||||
"--password" on the command-line!
|
||||
* #1015: When ctrl-C is pressed, more strongly encourage Borg to actually exit.
|
||||
* Add a "verify_tls" option to the Uptime Kuma monitoring hook for disabling TLS verification.
|
||||
* Add "tls" options to the MariaDB and MySQL database hooks to enable or disable TLS encryption
|
||||
between client and server.
|
||||
|
||||
1.9.12
|
||||
* #1005: Fix the credential hooks to avoid using Python 3.12+ string features. Now borgmatic will
|
||||
work with Python 3.9, 3.10, and 3.11 again.
|
||||
|
||||
@@ -74,7 +74,7 @@ def make_environment(config):
|
||||
os.write(write_file_descriptor, passphrase.encode('utf-8'))
|
||||
os.close(write_file_descriptor)
|
||||
|
||||
# This, plus subprocess.Popen(..., close_fds=False) in execute.py, is necessary for the Borg
|
||||
# This plus subprocess.Popen(..., close_fds=False) in execute.py is necessary for the Borg
|
||||
# child process to inherit the file descriptor.
|
||||
os.set_inheritable(read_file_descriptor, True)
|
||||
environment['BORG_PASSPHRASE_FD'] = str(read_file_descriptor)
|
||||
|
||||
@@ -41,6 +41,7 @@ def schema_to_sample_configuration(schema, level=0, parent_is_sequence=False):
|
||||
'''
|
||||
schema_type = schema.get('type')
|
||||
example = schema.get('example')
|
||||
|
||||
if example is not None:
|
||||
return example
|
||||
|
||||
|
||||
@@ -1040,6 +1040,18 @@ properties:
|
||||
individual databases. See the pg_dump documentation for
|
||||
more about formats.
|
||||
example: directory
|
||||
compression:
|
||||
type: ["string", "integer"]
|
||||
description: |
|
||||
Database dump compression level (integer) or method
|
||||
("gzip", "lz4", "zstd", or "none") and optional
|
||||
colon-separated detail. Defaults to moderate "gzip" for
|
||||
"custom" and "directory" formats and no compression for
|
||||
the "plain" format. Compression is not supported for the
|
||||
"tar" format. Be aware that Borg does its own
|
||||
compression as well, so you may not need it in both
|
||||
places.
|
||||
example: none
|
||||
ssl_mode:
|
||||
type: string
|
||||
enum: ['disable', 'allow', 'prefer',
|
||||
@@ -1198,6 +1210,20 @@ properties:
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
tls:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether to TLS-encrypt data transmitted between the
|
||||
client and server. The default varies based on the
|
||||
MariaDB version.
|
||||
example: false
|
||||
restore_tls:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether to TLS-encrypt data transmitted between the
|
||||
client and restore server. The default varies based on
|
||||
the MariaDB version.
|
||||
example: false
|
||||
mariadb_dump_command:
|
||||
type: string
|
||||
description: |
|
||||
@@ -1328,6 +1354,20 @@ properties:
|
||||
Defaults to the "password" option. Supports the
|
||||
"{credential ...}" syntax.
|
||||
example: trustsome1
|
||||
tls:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether to TLS-encrypt data transmitted between the
|
||||
client and server. The default varies based on the
|
||||
MySQL installation.
|
||||
example: false
|
||||
restore_tls:
|
||||
type: boolean
|
||||
description: |
|
||||
Whether to TLS-encrypt data transmitted between the
|
||||
client and restore server. The default varies based on
|
||||
the MySQL installation.
|
||||
example: false
|
||||
mysql_dump_command:
|
||||
type: string
|
||||
description: |
|
||||
@@ -1907,6 +1947,8 @@ properties:
|
||||
zabbix:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required:
|
||||
- server
|
||||
properties:
|
||||
itemid:
|
||||
type: integer
|
||||
@@ -1929,7 +1971,8 @@ properties:
|
||||
server:
|
||||
type: string
|
||||
description: |
|
||||
The address of your Zabbix instance.
|
||||
The API endpoint URL of your Zabbix instance, usually ending
|
||||
with "/api_jsonrpc.php". Required.
|
||||
example: https://zabbix.your-domain.com
|
||||
username:
|
||||
type: string
|
||||
@@ -2200,6 +2243,12 @@ properties:
|
||||
- start
|
||||
- finish
|
||||
- fail
|
||||
verify_tls:
|
||||
type: boolean
|
||||
description: |
|
||||
Verify the TLS certificate of the push URL host. Defaults to
|
||||
true.
|
||||
example: false
|
||||
description: |
|
||||
Configuration for a monitoring integration with Uptime Kuma using
|
||||
the Push monitor type.
|
||||
|
||||
+19
-16
@@ -266,8 +266,8 @@ def log_command(full_command, input_file=None, output_file=None, environment=Non
|
||||
width=MAX_LOGGED_COMMAND_LENGTH,
|
||||
placeholder=' ...',
|
||||
)
|
||||
+ (f" < {getattr(input_file, 'name', '')}" if input_file else '')
|
||||
+ (f" > {getattr(output_file, 'name', '')}" if output_file else '')
|
||||
+ (f" < {getattr(input_file, 'name', input_file)}" if input_file else '')
|
||||
+ (f" > {getattr(output_file, 'name', output_file)}" if output_file else '')
|
||||
)
|
||||
|
||||
|
||||
@@ -315,8 +315,8 @@ def execute_command(
|
||||
shell=shell,
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
# Necessary for passing credentials via anonymous pipe.
|
||||
close_fds=False,
|
||||
)
|
||||
if not run_to_completion:
|
||||
return process
|
||||
@@ -333,6 +333,7 @@ def execute_command(
|
||||
|
||||
def execute_command_and_capture_output(
|
||||
full_command,
|
||||
input_file=None,
|
||||
capture_stderr=False,
|
||||
shell=False,
|
||||
environment=None,
|
||||
@@ -342,28 +343,30 @@ def execute_command_and_capture_output(
|
||||
):
|
||||
'''
|
||||
Execute the given command (a sequence of command/argument strings), capturing and returning its
|
||||
output (stdout). If capture stderr is True, then capture and return stderr in addition to
|
||||
stdout. If shell is True, execute the command within a shell. If an environment variables dict
|
||||
is given, then pass it into the command. If a working directory is given, use that as the
|
||||
present working directory when running the command. If a Borg local path is given, and the
|
||||
command matches it (regardless of arguments), treat exit code 1 as a warning instead of an
|
||||
error. But if Borg exit codes are given as a sequence of exit code configuration dicts, then use
|
||||
that configuration to decide what's an error and what's a warning.
|
||||
output (stdout). If an input file descriptor is given, then pipe it to the command's stdin. If
|
||||
capture stderr is True, then capture and return stderr in addition to stdout. If shell is True,
|
||||
execute the command within a shell. If an environment variables dict is given, then pass it into
|
||||
the command. If a working directory is given, use that as the present working directory when
|
||||
running the command. If a Borg local path is given, and the command matches it (regardless of
|
||||
arguments), treat exit code 1 as a warning instead of an error. But if Borg exit codes are given
|
||||
as a sequence of exit code configuration dicts, then use that configuration to decide what's an
|
||||
error and what's a warning.
|
||||
|
||||
Raise subprocesses.CalledProcessError if an error occurs while running the command.
|
||||
'''
|
||||
log_command(full_command, environment=environment)
|
||||
log_command(full_command, input_file, environment=environment)
|
||||
command = ' '.join(full_command) if shell else full_command
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
command,
|
||||
stdin=input_file,
|
||||
stderr=subprocess.STDOUT if capture_stderr else None,
|
||||
shell=shell,
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
# Necessary for passing credentials via anonymous pipe.
|
||||
close_fds=False,
|
||||
)
|
||||
except subprocess.CalledProcessError as error:
|
||||
if (
|
||||
@@ -422,8 +425,8 @@ def execute_command_with_processes(
|
||||
shell=shell,
|
||||
env=environment,
|
||||
cwd=working_directory,
|
||||
# Necessary for the passcommand credential hook to work.
|
||||
close_fds=not bool((environment or {}).get('BORG_PASSPHRASE_FD')),
|
||||
# Necessary for passing credentials via anonymous pipe.
|
||||
close_fds=False,
|
||||
)
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
# Something has gone wrong. So vent each process' output buffer to prevent it from hanging.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import borgmatic.borg.pattern
|
||||
@@ -23,14 +24,90 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
return dump.make_data_source_dump_path(base_directory, 'mariadb_databases')
|
||||
|
||||
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
DEFAULTS_EXTRA_FILE_FLAG_PATTERN = re.compile('^--defaults-extra-file=(?P<filename>.*)$')
|
||||
|
||||
|
||||
def database_names_to_dump(database, config, environment, dry_run):
|
||||
def parse_extra_options(extra_options):
|
||||
'''
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all", query for the names of databases on the configured
|
||||
host and return them, excluding any system databases that will cause problems during restore.
|
||||
Given an extra options string, split the options into a tuple and return it. Additionally, if
|
||||
the first option is "--defaults-extra-file=...", then remove it from the options and return the
|
||||
filename.
|
||||
|
||||
So the return value is a tuple of: (parsed options, defaults extra filename).
|
||||
|
||||
The intent is to support downstream merging of multiple "--defaults-extra-file"s, as
|
||||
MariaDB/MySQL only allows one at a time.
|
||||
'''
|
||||
split_extra_options = tuple(shlex.split(extra_options)) if extra_options else ()
|
||||
|
||||
if not split_extra_options:
|
||||
return ((), None)
|
||||
|
||||
match = DEFAULTS_EXTRA_FILE_FLAG_PATTERN.match(split_extra_options[0])
|
||||
|
||||
if not match:
|
||||
return (split_extra_options, None)
|
||||
|
||||
return (split_extra_options[1:], match.group('filename'))
|
||||
|
||||
|
||||
def make_defaults_file_options(username=None, password=None, defaults_extra_filename=None):
|
||||
'''
|
||||
Given a database username and/or password, write it to an anonymous pipe and return the flags
|
||||
for passing that file descriptor to an executed command. The idea is that this is a more secure
|
||||
way to transmit credentials to a database client than using an environment variable.
|
||||
|
||||
If no username or password are given, then return the options for the given defaults extra
|
||||
filename (if any). But if there is a username and/or password and a defaults extra filename is
|
||||
given, then "!include" it from the generated file, effectively allowing multiple defaults extra
|
||||
files.
|
||||
|
||||
Do not use the returned value for multiple different command invocations. That will not work
|
||||
because each pipe is "used up" once read.
|
||||
'''
|
||||
values = '\n'.join(
|
||||
(
|
||||
(f'user={username}' if username is not None else ''),
|
||||
(f'password={password}' if password is not None else ''),
|
||||
)
|
||||
).strip()
|
||||
|
||||
if not values:
|
||||
if defaults_extra_filename:
|
||||
return (f'--defaults-extra-file={defaults_extra_filename}',)
|
||||
|
||||
return ()
|
||||
|
||||
fields_message = ' and '.join(
|
||||
field_name
|
||||
for field_name in (
|
||||
(f'username ({username})' if username is not None else None),
|
||||
('password' if password is not None else None),
|
||||
)
|
||||
if field_name is not None
|
||||
)
|
||||
include_message = f' (including {defaults_extra_filename})' if defaults_extra_filename else ''
|
||||
logger.debug(f'Writing database {fields_message} to defaults extra file pipe{include_message}')
|
||||
|
||||
include = f'!include {defaults_extra_filename}\n' if defaults_extra_filename else ''
|
||||
|
||||
read_file_descriptor, write_file_descriptor = os.pipe()
|
||||
os.write(write_file_descriptor, f'{include}[client]\n{values}'.encode('utf-8'))
|
||||
os.close(write_file_descriptor)
|
||||
|
||||
# This plus subprocess.Popen(..., close_fds=False) in execute.py is necessary for the database
|
||||
# client child process to inherit the file descriptor.
|
||||
os.set_inheritable(read_file_descriptor, True)
|
||||
|
||||
return (f'--defaults-extra-file=/dev/fd/{read_file_descriptor}',)
|
||||
|
||||
|
||||
def database_names_to_dump(database, config, username, password, environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, a configuration dict, a database username and password, an
|
||||
environment dict, and whether this is a dry run, return the corresponding sequence of database
|
||||
names to dump. In the case of "all", query for the names of databases on the configured host and
|
||||
return them, excluding any system databases that will cause problems during restore.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@@ -40,24 +117,22 @@ def database_names_to_dump(database, config, environment, dry_run):
|
||||
mariadb_show_command = tuple(
|
||||
shlex.quote(part) for part in shlex.split(database.get('mariadb_command') or 'mariadb')
|
||||
)
|
||||
extra_options, defaults_extra_filename = parse_extra_options(database.get('list_options'))
|
||||
show_command = (
|
||||
mariadb_show_command
|
||||
+ (tuple(database['list_options'].split(' ')) if 'list_options' in database else ())
|
||||
+ make_defaults_file_options(username, password, defaults_extra_filename)
|
||||
+ extra_options
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (('--ssl',) if database.get('tls') is True else ())
|
||||
+ (('--skip-ssl',) if database.get('tls') is False else ())
|
||||
+ ('--skip-column-names', '--batch')
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
|
||||
logger.debug('Querying for "all" MariaDB databases to dump')
|
||||
|
||||
show_output = execute_command_and_capture_output(show_command, environment=environment)
|
||||
|
||||
return tuple(
|
||||
@@ -67,8 +142,19 @@ def database_names_to_dump(database, config, environment, dry_run):
|
||||
)
|
||||
|
||||
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, config, dump_path, database_names, environment, dry_run, dry_run_label
|
||||
database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
database_names,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MariaDB database (provided as a configuration dict) to a named
|
||||
@@ -95,21 +181,17 @@ def execute_dump_command(
|
||||
shlex.quote(part)
|
||||
for part in shlex.split(database.get('mariadb_dump_command') or 'mariadb-dump')
|
||||
)
|
||||
extra_options, defaults_extra_filename = parse_extra_options(database.get('options'))
|
||||
dump_command = (
|
||||
mariadb_dump_command
|
||||
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
|
||||
+ make_defaults_file_options(username, password, defaults_extra_filename)
|
||||
+ extra_options
|
||||
+ (('--add-drop-database',) if database.get('add_drop_database', True) else ())
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (('--ssl',) if database.get('tls') is True else ())
|
||||
+ (('--skip-ssl',) if database.get('tls') is False else ())
|
||||
+ ('--databases',)
|
||||
+ database_names
|
||||
+ ('--result-file', dump_filename)
|
||||
@@ -165,19 +247,16 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
environment = dict(
|
||||
os.environ,
|
||||
**(
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else {}
|
||||
),
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database.get('password'), config
|
||||
)
|
||||
environment = dict(os.environ)
|
||||
dump_database_names = database_names_to_dump(
|
||||
database, config, username, password, environment, dry_run
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, config, environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@@ -193,6 +272,8 @@ def dump_data_sources(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
environment,
|
||||
@@ -205,6 +286,8 @@ def dump_data_sources(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
environment,
|
||||
@@ -278,6 +361,7 @@ def restore_data_source_dump(
|
||||
port = str(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
tls = data_source.get('restore_tls', data_source.get('tls'))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
(
|
||||
connection_params['username']
|
||||
@@ -296,20 +380,19 @@ def restore_data_source_dump(
|
||||
mariadb_restore_command = tuple(
|
||||
shlex.quote(part) for part in shlex.split(data_source.get('mariadb_command') or 'mariadb')
|
||||
)
|
||||
extra_options, defaults_extra_filename = parse_extra_options(data_source.get('restore_options'))
|
||||
restore_command = (
|
||||
mariadb_restore_command
|
||||
+ make_defaults_file_options(username, password, defaults_extra_filename)
|
||||
+ extra_options
|
||||
+ ('--batch',)
|
||||
+ (
|
||||
tuple(data_source['restore_options'].split(' '))
|
||||
if 'restore_options' in data_source
|
||||
else ()
|
||||
)
|
||||
+ (('--host', hostname) if hostname else ())
|
||||
+ (('--port', str(port)) if port else ())
|
||||
+ (('--protocol', 'tcp') if hostname or port else ())
|
||||
+ (('--user', username) if username else ())
|
||||
+ (('--ssl',) if tls is True else ())
|
||||
+ (('--skip-ssl',) if tls is False else ())
|
||||
)
|
||||
environment = dict(os.environ, **({'MYSQL_PWD': password} if password else {}))
|
||||
environment = dict(os.environ)
|
||||
|
||||
logger.debug(f"Restoring MariaDB database {data_source['name']}{dry_run_label}")
|
||||
if dry_run:
|
||||
|
||||
@@ -89,12 +89,36 @@ def dump_data_sources(
|
||||
return processes
|
||||
|
||||
|
||||
def make_password_config_file(password):
|
||||
'''
|
||||
Given a database password, write it as a MongoDB configuration file to an anonymous pipe and
|
||||
return its filename. The idea is that this is a more secure way to transmit a password to
|
||||
MongoDB than providing it directly on the command-line.
|
||||
|
||||
Do not use the returned value for multiple different command invocations. That will not work
|
||||
because each pipe is "used up" once read.
|
||||
'''
|
||||
logger.debug('Writing MongoDB password to configuration file pipe')
|
||||
|
||||
read_file_descriptor, write_file_descriptor = os.pipe()
|
||||
os.write(write_file_descriptor, f'password: {password}'.encode('utf-8'))
|
||||
os.close(write_file_descriptor)
|
||||
|
||||
# This plus subprocess.Popen(..., close_fds=False) in execute.py is necessary for the database
|
||||
# client child process to inherit the file descriptor.
|
||||
os.set_inheritable(read_file_descriptor, True)
|
||||
|
||||
return f'/dev/fd/{read_file_descriptor}'
|
||||
|
||||
|
||||
def build_dump_command(database, config, dump_filename, dump_format):
|
||||
'''
|
||||
Return the mongodump command from a single database configuration.
|
||||
'''
|
||||
all_databases = database['name'] == 'all'
|
||||
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(database.get('password'), config)
|
||||
|
||||
return (
|
||||
('mongodump',)
|
||||
+ (('--out', shlex.quote(dump_filename)) if dump_format == 'directory' else ())
|
||||
@@ -112,18 +136,7 @@ def build_dump_command(database, config, dump_filename, dump_format):
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (
|
||||
(
|
||||
'--password',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'password' in database
|
||||
else ()
|
||||
)
|
||||
+ (('--config', make_password_config_file(password)) if password else ())
|
||||
+ (
|
||||
('--authenticationDatabase', shlex.quote(database['authentication_database']))
|
||||
if 'authentication_database' in database
|
||||
@@ -251,7 +264,7 @@ def build_restore_command(extract_process, database, config, dump_filename, conn
|
||||
if username:
|
||||
command.extend(('--username', username))
|
||||
if password:
|
||||
command.extend(('--password', password))
|
||||
command.extend(('--config', make_password_config_file(password)))
|
||||
if 'authentication_database' in database:
|
||||
command.extend(('--authenticationDatabase', database['authentication_database']))
|
||||
if 'restore_options' in database:
|
||||
|
||||
@@ -6,6 +6,7 @@ import shlex
|
||||
import borgmatic.borg.pattern
|
||||
import borgmatic.config.paths
|
||||
import borgmatic.hooks.credential.parse
|
||||
import borgmatic.hooks.data_source.mariadb
|
||||
from borgmatic.execute import (
|
||||
execute_command,
|
||||
execute_command_and_capture_output,
|
||||
@@ -26,11 +27,12 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def database_names_to_dump(database, config, environment, dry_run):
|
||||
def database_names_to_dump(database, config, username, password, environment, dry_run):
|
||||
'''
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all", query for the names of databases on the configured
|
||||
host and return them, excluding any system databases that will cause problems during restore.
|
||||
Given a requested database config, a configuration dict, a database username and password, an
|
||||
environment dict, and whether this is a dry run, return the corresponding sequence of database
|
||||
names to dump. In the case of "all", query for the names of databases on the configured host and
|
||||
return them, excluding any system databases that will cause problems during restore.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@@ -40,24 +42,26 @@ def database_names_to_dump(database, config, environment, dry_run):
|
||||
mysql_show_command = tuple(
|
||||
shlex.quote(part) for part in shlex.split(database.get('mysql_command') or 'mysql')
|
||||
)
|
||||
extra_options, defaults_extra_filename = (
|
||||
borgmatic.hooks.data_source.mariadb.parse_extra_options(database.get('list_options'))
|
||||
)
|
||||
show_command = (
|
||||
mysql_show_command
|
||||
+ (tuple(database['list_options'].split(' ')) if 'list_options' in database else ())
|
||||
+ borgmatic.hooks.data_source.mariadb.make_defaults_file_options(
|
||||
username, password, defaults_extra_filename
|
||||
)
|
||||
+ extra_options
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (('--ssl',) if database.get('tls') is True else ())
|
||||
+ (('--skip-ssl',) if database.get('tls') is False else ())
|
||||
+ ('--skip-column-names', '--batch')
|
||||
+ ('--execute', 'show schemas')
|
||||
)
|
||||
|
||||
logger.debug('Querying for "all" MySQL databases to dump')
|
||||
|
||||
show_output = execute_command_and_capture_output(show_command, environment=environment)
|
||||
|
||||
return tuple(
|
||||
@@ -68,7 +72,15 @@ def database_names_to_dump(database, config, environment, dry_run):
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, config, dump_path, database_names, environment, dry_run, dry_run_label
|
||||
database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
database_names,
|
||||
environment,
|
||||
dry_run,
|
||||
dry_run_label,
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MySQL/MariaDB database (provided as a configuration dict) to a
|
||||
@@ -94,21 +106,21 @@ def execute_dump_command(
|
||||
mysql_dump_command = tuple(
|
||||
shlex.quote(part) for part in shlex.split(database.get('mysql_dump_command') or 'mysqldump')
|
||||
)
|
||||
extra_options, defaults_extra_filename = (
|
||||
borgmatic.hooks.data_source.mariadb.parse_extra_options(database.get('options'))
|
||||
)
|
||||
dump_command = (
|
||||
mysql_dump_command
|
||||
+ (tuple(database['options'].split(' ')) if 'options' in database else ())
|
||||
+ borgmatic.hooks.data_source.mariadb.make_defaults_file_options(
|
||||
username, password, defaults_extra_filename
|
||||
)
|
||||
+ extra_options
|
||||
+ (('--add-drop-database',) if database.get('add_drop_database', True) else ())
|
||||
+ (('--host', database['hostname']) if 'hostname' in database else ())
|
||||
+ (('--port', str(database['port'])) if 'port' in database else ())
|
||||
+ (('--protocol', 'tcp') if 'hostname' in database or 'port' in database else ())
|
||||
+ (
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
+ (('--ssl',) if database.get('tls') is True else ())
|
||||
+ (('--skip-ssl',) if database.get('tls') is False else ())
|
||||
+ ('--databases',)
|
||||
+ database_names
|
||||
+ ('--result-file', dump_filename)
|
||||
@@ -164,19 +176,16 @@ def dump_data_sources(
|
||||
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
environment = dict(
|
||||
os.environ,
|
||||
**(
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else {}
|
||||
),
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database.get('password'), config
|
||||
)
|
||||
environment = dict(os.environ)
|
||||
dump_database_names = database_names_to_dump(
|
||||
database, config, username, password, environment, dry_run
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, config, environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@@ -192,6 +201,8 @@ def dump_data_sources(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
environment,
|
||||
@@ -204,6 +215,8 @@ def dump_data_sources(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
username,
|
||||
password,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
environment,
|
||||
@@ -277,6 +290,7 @@ def restore_data_source_dump(
|
||||
port = str(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
tls = data_source.get('restore_tls', data_source.get('tls'))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
(
|
||||
connection_params['username']
|
||||
@@ -295,20 +309,23 @@ def restore_data_source_dump(
|
||||
mysql_restore_command = tuple(
|
||||
shlex.quote(part) for part in shlex.split(data_source.get('mysql_command') or 'mysql')
|
||||
)
|
||||
extra_options, defaults_extra_filename = (
|
||||
borgmatic.hooks.data_source.mariadb.parse_extra_options(data_source.get('restore_options'))
|
||||
)
|
||||
restore_command = (
|
||||
mysql_restore_command
|
||||
+ ('--batch',)
|
||||
+ (
|
||||
tuple(data_source['restore_options'].split(' '))
|
||||
if 'restore_options' in data_source
|
||||
else ()
|
||||
+ borgmatic.hooks.data_source.mariadb.make_defaults_file_options(
|
||||
username, password, defaults_extra_filename
|
||||
)
|
||||
+ extra_options
|
||||
+ ('--batch',)
|
||||
+ (('--host', hostname) if hostname else ())
|
||||
+ (('--port', str(port)) if port else ())
|
||||
+ (('--protocol', 'tcp') if hostname or port else ())
|
||||
+ (('--user', username) if username else ())
|
||||
+ (('--ssl',) if tls is True else ())
|
||||
+ (('--skip-ssl',) if tls is False else ())
|
||||
)
|
||||
environment = dict(os.environ, **({'MYSQL_PWD': password} if password else {}))
|
||||
environment = dict(os.environ)
|
||||
|
||||
logger.debug(f"Restoring MySQL database {data_source['name']}{dry_run_label}")
|
||||
if dry_run:
|
||||
|
||||
@@ -159,6 +159,7 @@ def dump_data_sources(
|
||||
|
||||
for database_name in dump_database_names:
|
||||
dump_format = database.get('format', None if database_name == 'all' else 'custom')
|
||||
compression = database.get('compression')
|
||||
default_dump_command = 'pg_dumpall' if database_name == 'all' else 'pg_dump'
|
||||
dump_command = tuple(
|
||||
shlex.quote(part)
|
||||
@@ -199,6 +200,7 @@ def dump_data_sources(
|
||||
)
|
||||
+ (('--no-owner',) if database.get('no_owner', False) else ())
|
||||
+ (('--format', shlex.quote(dump_format)) if dump_format else ())
|
||||
+ (('--compress', shlex.quote(str(compression))) if compression is not None else ())
|
||||
+ (('--file', shlex.quote(dump_filename)) if dump_format == 'directory' else ())
|
||||
+ (
|
||||
tuple(shlex.quote(option) for option in database['options'].split(' '))
|
||||
|
||||
@@ -134,7 +134,16 @@ def get_all_dataset_mount_points(zfs_command):
|
||||
)
|
||||
)
|
||||
|
||||
return tuple(sorted(line.rstrip() for line in list_output.splitlines()))
|
||||
return tuple(
|
||||
sorted(
|
||||
{
|
||||
mount_point
|
||||
for line in list_output.splitlines()
|
||||
for mount_point in (line.rstrip(),)
|
||||
if mount_point != 'none'
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def snapshot_dataset(zfs_command, full_snapshot_name): # pragma: no cover
|
||||
@@ -411,7 +420,7 @@ def remove_data_source_dumps(hook_config, config, borgmatic_runtime_directory, d
|
||||
continue
|
||||
|
||||
if not dry_run:
|
||||
shutil.rmtree(snapshots_directory)
|
||||
shutil.rmtree(snapshot_mount_path, ignore_errors=True)
|
||||
|
||||
# Destroy snapshots.
|
||||
full_snapshot_names = get_all_snapshots(zfs_command)
|
||||
|
||||
@@ -37,7 +37,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
|
||||
try:
|
||||
response = requests.get(f'{push_url}?{query}')
|
||||
response = requests.get(f'{push_url}?{query}', verify=hook_config.get('verify_tls', True))
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as error:
|
||||
|
||||
@@ -16,6 +16,42 @@ def initialize_monitor(
|
||||
pass
|
||||
|
||||
|
||||
def send_zabbix_request(server, headers, data):
|
||||
'''
|
||||
Given a Zabbix server URL, HTTP headers as a dict, and valid Zabbix JSON payload data as a dict,
|
||||
send a request to the Zabbix server via API.
|
||||
|
||||
Return the response "result" value or None.
|
||||
'''
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
|
||||
logger.debug(f'Sending a "{data["method"]}" request to the Zabbix server')
|
||||
|
||||
try:
|
||||
response = requests.post(server, headers=headers, json=data)
|
||||
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as error:
|
||||
logger.warning(f'Zabbix error: {error}')
|
||||
|
||||
return None
|
||||
|
||||
try:
|
||||
result = response.json().get('result')
|
||||
error_message = result['data'][0]['error']
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
logger.warning('Zabbix error: Cannot parse API response')
|
||||
|
||||
return None
|
||||
except (TypeError, KeyError, IndexError):
|
||||
return result
|
||||
else:
|
||||
logger.warning(f'Zabbix error: {error_message}')
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run):
|
||||
'''
|
||||
Update the configured Zabbix item using either the itemid, or a host and key.
|
||||
@@ -48,6 +84,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Zabbix credential error: {error}')
|
||||
|
||||
return
|
||||
|
||||
server = hook_config.get('server')
|
||||
@@ -57,13 +94,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
value = state_config.get('value')
|
||||
headers = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
logger.info(f'Updating Zabbix{dry_run_label}')
|
||||
logger.info(f'Pinging Zabbix{dry_run_label}')
|
||||
logger.debug(f'Using Zabbix URL: {server}')
|
||||
|
||||
if server is None:
|
||||
logger.warning('Server missing for Zabbix')
|
||||
return
|
||||
|
||||
# Determine the Zabbix method used to store the value: itemid or host/key
|
||||
if itemid is not None:
|
||||
logger.info(f'Updating {itemid} on Zabbix')
|
||||
@@ -74,8 +107,8 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
'id': 1,
|
||||
}
|
||||
|
||||
elif (host and key) is not None:
|
||||
logger.info(f'Updating Host:{host} and Key:{key} on Zabbix')
|
||||
elif host is not None and key is not None:
|
||||
logger.info(f'Updating Host: "{host}" and Key: "{key}" on Zabbix')
|
||||
data = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'history.push',
|
||||
@@ -85,58 +118,63 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
elif host is not None:
|
||||
logger.warning('Key missing for Zabbix')
|
||||
return
|
||||
|
||||
return
|
||||
elif key is not None:
|
||||
logger.warning('Host missing for Zabbix')
|
||||
|
||||
return
|
||||
else:
|
||||
logger.warning('No Zabbix itemid or host/key provided')
|
||||
|
||||
return
|
||||
|
||||
# Determine the authentication method: API key or username/password
|
||||
if api_key is not None:
|
||||
logger.info('Using API key auth for Zabbix')
|
||||
headers['Authorization'] = 'Bearer ' + api_key
|
||||
|
||||
elif (username and password) is not None:
|
||||
logger.info('Using user/pass auth with user {username} for Zabbix')
|
||||
auth_data = {
|
||||
headers['Authorization'] = f'Bearer {api_key}'
|
||||
elif username is not None and password is not None:
|
||||
logger.info(f'Using user/pass auth with user {username} for Zabbix')
|
||||
login_data = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'user.login',
|
||||
'params': {'username': username, 'password': password},
|
||||
'id': 1,
|
||||
}
|
||||
|
||||
if not dry_run:
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
try:
|
||||
response = requests.post(server, headers=headers, json=auth_data)
|
||||
data['auth'] = response.json().get('result')
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as error:
|
||||
logger.warning(f'Zabbix error: {error}')
|
||||
result = send_zabbix_request(server, headers, login_data)
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
||||
headers['Authorization'] = f'Bearer {result}'
|
||||
elif username is not None:
|
||||
logger.warning('Password missing for Zabbix authentication')
|
||||
return
|
||||
|
||||
return
|
||||
elif password is not None:
|
||||
logger.warning('Username missing for Zabbix authentication')
|
||||
|
||||
return
|
||||
else:
|
||||
logger.warning('Authentication data missing for Zabbix')
|
||||
|
||||
return
|
||||
|
||||
if not dry_run:
|
||||
logging.getLogger('urllib3').setLevel(logging.ERROR)
|
||||
try:
|
||||
response = requests.post(server, headers=headers, json=data)
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.RequestException as error:
|
||||
logger.warning(f'Zabbix error: {error}')
|
||||
send_zabbix_request(server, headers, data)
|
||||
|
||||
if username is not None and password is not None:
|
||||
logout_data = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'user.logout',
|
||||
'params': [],
|
||||
'id': 1,
|
||||
}
|
||||
|
||||
if not dry_run:
|
||||
send_zabbix_request(server, headers, logout_data)
|
||||
|
||||
|
||||
def destroy_monitor(ping_url_or_uuid, config, monitoring_log_level, dry_run): # pragma: no cover
|
||||
|
||||
@@ -24,6 +24,9 @@ def handle_signal(signal_number, frame):
|
||||
logger.critical('Exiting due to TERM signal')
|
||||
sys.exit(EXIT_CODE_FROM_SIGNAL + signal.SIGTERM)
|
||||
elif signal_number == signal.SIGINT:
|
||||
# Borg doesn't always exit on a SIGINT, so give it a little encouragement.
|
||||
os.killpg(os.getpgrp(), signal.SIGTERM)
|
||||
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@ def list_merged_pulls(url):
|
||||
|
||||
|
||||
def list_contributing_issues(url):
|
||||
# labels = bug, design finalized, etc.
|
||||
response = requests.get(f'{url}?labels=19,20,22,23,32,52,53,54', headers={'Accept': 'application/json', 'Content-Type': 'application/json'})
|
||||
response = requests.get(url, headers={'Accept': 'application/json', 'Content-Type': 'application/json'})
|
||||
|
||||
if not response.ok:
|
||||
response.raise_for_status()
|
||||
@@ -39,7 +38,7 @@ PULLS_API_ENDPOINT_URLS = (
|
||||
'https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/pulls',
|
||||
'https://api.github.com/repos/borgmatic-collective/borgmatic/pulls',
|
||||
)
|
||||
ISSUES_API_ENDPOINT_URL = 'https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/issues'
|
||||
ISSUES_API_ENDPOINT_URL = 'https://projects.torsion.org/api/v1/repos/borgmatic-collective/borgmatic/issues?state=all'
|
||||
RECENT_CONTRIBUTORS_CUTOFF_DAYS = 365
|
||||
|
||||
|
||||
|
||||
@@ -309,10 +309,8 @@ hooks:
|
||||
### External passwords
|
||||
|
||||
If you don't want to keep your database passwords in your borgmatic
|
||||
configuration file, you can instead pass them in via [environment
|
||||
variables](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/)
|
||||
or command-line [configuration
|
||||
overrides](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#configuration-overrides).
|
||||
configuration file, you can instead pass them in [from external credential
|
||||
sources](https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/).
|
||||
|
||||
|
||||
### Configuration backups
|
||||
|
||||
@@ -710,8 +710,10 @@ zabbix:
|
||||
- fail
|
||||
```
|
||||
|
||||
This hook requires the Zabbix server be running version 7.0. ([Support for Zabbix
|
||||
7.2+](https://projects.torsion.org/borgmatic-collective/borgmatic/issues/1003) is planned.)
|
||||
This hook requires the Zabbix server be running version 7.0.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.3</span> Zabbix 7.2+
|
||||
is supported as well.
|
||||
|
||||
|
||||
### Authentication methods
|
||||
|
||||
@@ -205,6 +205,14 @@ Volume Manager) and sending those snapshots to Borg for backup. LVM isn't
|
||||
itself a filesystem, but it can take snapshots at the layer right below your
|
||||
filesystem.
|
||||
|
||||
Note that, due to Borg being a file-level backup, this feature is really only
|
||||
suitable for filesystems, not whole disk or raw images containing multiple
|
||||
filesystems (for example, if you're using a LVM volume to run a Windows
|
||||
KVM that contains an MBR, partitions, etc.).
|
||||
|
||||
In those cases, you can omit the `lvm:` option and use Borg's own support for
|
||||
[image backup](https://borgbackup.readthedocs.io/en/stable/deployment/image-backup.html).
|
||||
|
||||
To use this feature, first you need one or more mounted LVM logical volumes.
|
||||
Then, enable LVM within borgmatic by adding the following line to your
|
||||
configuration file:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "borgmatic"
|
||||
version = "1.9.12"
|
||||
version = "1.9.13"
|
||||
authors = [
|
||||
{ name="Dan Helfman", email="witten@torsion.org" },
|
||||
]
|
||||
|
||||
@@ -6,10 +6,108 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.data_source import mariadb as module
|
||||
|
||||
|
||||
def test_parse_extra_options_passes_through_empty_options():
|
||||
assert module.parse_extra_options('') == ((), None)
|
||||
|
||||
|
||||
def test_parse_extra_options_with_defaults_extra_file_removes_and_and_parses_out_filename():
|
||||
assert module.parse_extra_options('--defaults-extra-file=extra.cnf --skip-ssl') == (
|
||||
('--skip-ssl',),
|
||||
'extra.cnf',
|
||||
)
|
||||
|
||||
|
||||
def test_parse_extra_options_without_defaults_extra_file_passes_through_options():
|
||||
assert module.parse_extra_options('--skip-ssl --and=stuff') == (
|
||||
('--skip-ssl', '--and=stuff'),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
def test_make_defaults_file_pipe_without_username_or_password_bails():
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
|
||||
assert module.make_defaults_file_options(username=None, password=None) == ()
|
||||
|
||||
|
||||
def test_make_defaults_file_option_with_username_and_password_writes_them_to_file_descriptor():
|
||||
read_descriptor = 99
|
||||
write_descriptor = flexmock()
|
||||
|
||||
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
|
||||
flexmock(module.os).should_receive('write').with_args(
|
||||
write_descriptor, b'[client]\nuser=root\npassword=trustsome1'
|
||||
).once()
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
assert module.make_defaults_file_options(username='root', password='trustsome1') == (
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
)
|
||||
|
||||
|
||||
def test_make_defaults_file_pipe_with_only_username_writes_it_to_file_descriptor():
|
||||
read_descriptor = 99
|
||||
write_descriptor = flexmock()
|
||||
|
||||
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
|
||||
flexmock(module.os).should_receive('write').with_args(
|
||||
write_descriptor, b'[client]\nuser=root'
|
||||
).once()
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
assert module.make_defaults_file_options(username='root', password=None) == (
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
)
|
||||
|
||||
|
||||
def test_make_defaults_file_pipe_with_only_password_writes_it_to_file_descriptor():
|
||||
read_descriptor = 99
|
||||
write_descriptor = flexmock()
|
||||
|
||||
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
|
||||
flexmock(module.os).should_receive('write').with_args(
|
||||
write_descriptor, b'[client]\npassword=trustsome1'
|
||||
).once()
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
assert module.make_defaults_file_options(username=None, password='trustsome1') == (
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
)
|
||||
|
||||
|
||||
def test_make_defaults_file_option_with_defaults_extra_filename_includes_it_in_file_descriptor():
|
||||
read_descriptor = 99
|
||||
write_descriptor = flexmock()
|
||||
|
||||
flexmock(module.os).should_receive('pipe').and_return(read_descriptor, write_descriptor)
|
||||
flexmock(module.os).should_receive('write').with_args(
|
||||
write_descriptor, b'!include extra.cnf\n[client]\nuser=root\npassword=trustsome1'
|
||||
).once()
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
assert module.make_defaults_file_options(
|
||||
username='root', password='trustsome1', defaults_extra_filename='extra.cnf'
|
||||
) == ('--defaults-extra-file=/dev/fd/99',)
|
||||
|
||||
|
||||
def test_make_defaults_file_option_with_only_defaults_extra_filename_uses_it_instead_of_file_descriptor():
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
|
||||
assert module.make_defaults_file_options(
|
||||
username=None, password=None, defaults_extra_filename='extra.cnf'
|
||||
) == ('--defaults-extra-file=extra.cnf',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, environment, dry_run=False)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'foo'}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
@@ -18,7 +116,9 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
environment = flexmock()
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=True)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=True
|
||||
)
|
||||
|
||||
assert names == ()
|
||||
|
||||
@@ -28,12 +128,83 @@ def test_database_names_to_dump_queries_mariadb_for_database_names():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=False)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mariadb_with_tls():
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all', 'tls': True}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mariadb_without_tls():
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all', 'tls': False}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@@ -53,6 +224,9 @@ def test_dump_data_sources_dumps_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
@@ -65,6 +239,8 @@ def test_dump_data_sources_dumps_each_database():
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
environment={'USER': 'root'},
|
||||
@@ -89,10 +265,10 @@ def test_dump_data_sources_dumps_with_password():
|
||||
database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -100,9 +276,11 @@ def test_dump_data_sources_dumps_with_password():
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -121,14 +299,16 @@ def test_dump_data_sources_dumps_all_databases_at_once():
|
||||
databases = [{'name': 'all'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': 'all'},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
environment={'USER': 'root'},
|
||||
@@ -150,16 +330,18 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
databases = [{'name': 'all', 'format': 'sql'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name, 'format': 'sql'},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
environment={'USER': 'root'},
|
||||
@@ -181,11 +363,18 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mariadb_with_list_options():
|
||||
database = {'name': 'all', 'list_options': '--defaults-extra-file=mariadb.cnf'}
|
||||
database = {'name': 'all', 'list_options': '--defaults-extra-file=mariadb.cnf --skip-ssl'}
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(
|
||||
('--skip-ssl',), 'mariadb.cnf'
|
||||
)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', 'mariadb.cnf'
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=mariadb.cnf',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
@@ -194,20 +383,30 @@ def test_database_names_to_dump_runs_mariadb_with_list_options():
|
||||
environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
database = {
|
||||
'name': 'all',
|
||||
'list_options': '--defaults-extra-file=mariadb.cnf',
|
||||
'list_options': '--defaults-extra-file=mariadb.cnf --skip-ssl',
|
||||
'mariadb_command': 'custom_mariadb',
|
||||
}
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(
|
||||
('--skip-ssl',), 'mariadb.cnf'
|
||||
)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', 'mariadb.cnf'
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
environment=None,
|
||||
full_command=(
|
||||
'custom_mariadb', # Custom MariaDB command
|
||||
'--defaults-extra-file=mariadb.cnf',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
@@ -215,7 +414,10 @@ def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
),
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mariadb_dump():
|
||||
@@ -225,11 +427,16 @@ def test_execute_dump_command_runs_mariadb_dump():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
'foo',
|
||||
@@ -244,6 +451,8 @@ def test_execute_dump_command_runs_mariadb_dump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -261,11 +470,16 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
@@ -279,6 +493,8 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -296,11 +512,16 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--host',
|
||||
'database.example.org',
|
||||
@@ -321,6 +542,96 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
== process
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mariadb_dump_with_tls():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--ssl',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'tls': True},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
== process
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mariadb_dump_without_tls():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--skip-ssl',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'tls': False},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -338,20 +649,23 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--user',
|
||||
'root',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -359,9 +673,11 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={},
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -376,11 +692,16 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(('--stuff=such',), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mariadb-dump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--stuff=such',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
@@ -396,6 +717,8 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -413,11 +736,16 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(('--stuff=such',), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'custom_mariadb_dump', # Custom MariaDB dump command
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--stuff=such',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
@@ -437,6 +765,8 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
'options': '--stuff=such',
|
||||
}, # Custom MariaDB dump command specified
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -450,6 +780,10 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@@ -457,6 +791,8 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -473,6 +809,10 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -481,6 +821,8 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -546,6 +888,10 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch'),
|
||||
@@ -578,9 +924,13 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(('--harder',), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--harder'),
|
||||
('mariadb', '--harder', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
@@ -612,9 +962,13 @@ def test_restore_data_source_dump_runs_non_default_mariadb_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return(('--harder',), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mariadb', '--batch', '--harder'),
|
||||
('custom_mariadb', '--harder', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
@@ -644,6 +998,10 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
@@ -678,6 +1036,86 @@ def test_restore_data_source_dump_runs_mariadb_with_hostname_and_port():
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mariadb_with_tls():
|
||||
hook_config = [{'name': 'foo', 'tls': True}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--batch',
|
||||
'--ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
hook_config,
|
||||
{},
|
||||
data_source=hook_config[0],
|
||||
dry_run=False,
|
||||
extract_process=extract_process,
|
||||
connection_params={
|
||||
'hostname': None,
|
||||
'port': None,
|
||||
'username': None,
|
||||
'password': None,
|
||||
},
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mariadb_without_tls():
|
||||
hook_config = [{'name': 'foo', 'tls': False}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--batch',
|
||||
'--skip-ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
hook_config,
|
||||
{},
|
||||
data_source=hook_config[0],
|
||||
dry_run=False,
|
||||
extract_process=extract_process,
|
||||
connection_params={
|
||||
'hostname': None,
|
||||
'port': None,
|
||||
'username': None,
|
||||
'password': None,
|
||||
},
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
|
||||
hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
@@ -685,13 +1123,17 @@ def test_restore_data_source_dump_runs_mariadb_with_username_and_password():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'root', 'trustsome1', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--user', 'root'),
|
||||
('mariadb', '--defaults-extra-file=/dev/fd/99', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -727,10 +1169,15 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'cliusername', 'clipassword', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--batch',
|
||||
'--host',
|
||||
'clihost',
|
||||
@@ -738,13 +1185,11 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
'cliport',
|
||||
'--protocol',
|
||||
'tcp',
|
||||
'--user',
|
||||
'cliusername',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'clipassword'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -771,10 +1216,12 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'password': 'trustsome1',
|
||||
'hostname': 'dbhost',
|
||||
'port': 'dbport',
|
||||
'tls': True,
|
||||
'restore_username': 'restoreuser',
|
||||
'restore_password': 'restorepass',
|
||||
'restore_hostname': 'restorehost',
|
||||
'restore_port': 'restoreport',
|
||||
'restore_tls': False,
|
||||
}
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
@@ -782,10 +1229,15 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
'restoreuser', 'restorepass', None
|
||||
).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--batch',
|
||||
'--host',
|
||||
'restorehost',
|
||||
@@ -793,13 +1245,12 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'restoreport',
|
||||
'--protocol',
|
||||
'tcp',
|
||||
'--user',
|
||||
'restoreuser',
|
||||
'--skip-ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'restorepass'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -824,6 +1275,10 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('parse_extra_options').and_return((), None)
|
||||
flexmock(module).should_receive('make_defaults_file_options').with_args(
|
||||
None, None, None
|
||||
).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
|
||||
@@ -127,6 +127,9 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_password_config_file').with_args('trustsome1').and_return(
|
||||
'/dev/fd/99'
|
||||
)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@@ -134,8 +137,8 @@ def test_dump_data_sources_runs_mongodump_with_username_and_password():
|
||||
'mongodump',
|
||||
'--username',
|
||||
'mongo',
|
||||
'--password',
|
||||
'trustsome1',
|
||||
'--config',
|
||||
'/dev/fd/99',
|
||||
'--authenticationDatabase',
|
||||
'admin',
|
||||
'--db',
|
||||
@@ -243,6 +246,22 @@ def test_dump_data_sources_runs_mongodumpall_for_all_databases():
|
||||
) == [process]
|
||||
|
||||
|
||||
def test_make_password_config_file_writes_password_to_pipe():
|
||||
read_file_descriptor = 99
|
||||
write_file_descriptor = flexmock()
|
||||
|
||||
flexmock(module.os).should_receive('pipe').and_return(
|
||||
(read_file_descriptor, write_file_descriptor)
|
||||
)
|
||||
flexmock(module.os).should_receive('write').with_args(
|
||||
write_file_descriptor, b'password: trustsome1'
|
||||
).once()
|
||||
flexmock(module.os).should_receive('close')
|
||||
flexmock(module.os).should_receive('set_inheritable')
|
||||
|
||||
assert module.make_password_config_file('trustsome1') == '/dev/fd/99'
|
||||
|
||||
|
||||
def test_build_dump_command_with_username_injection_attack_gets_escaped():
|
||||
database = {'name': 'test', 'username': 'bob; naughty-command'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
@@ -345,6 +364,9 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_password_config_file').with_args('trustsome1').and_return(
|
||||
'/dev/fd/99'
|
||||
)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -352,8 +374,8 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
|
||||
'--drop',
|
||||
'--username',
|
||||
'mongo',
|
||||
'--password',
|
||||
'trustsome1',
|
||||
'--config',
|
||||
'/dev/fd/99',
|
||||
'--authenticationDatabase',
|
||||
'admin',
|
||||
],
|
||||
@@ -399,6 +421,9 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_password_config_file').with_args(
|
||||
'clipassword'
|
||||
).and_return('/dev/fd/99')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -410,8 +435,8 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
'cliport',
|
||||
'--username',
|
||||
'cliusername',
|
||||
'--password',
|
||||
'clipassword',
|
||||
'--config',
|
||||
'/dev/fd/99',
|
||||
'--authenticationDatabase',
|
||||
'admin',
|
||||
],
|
||||
@@ -457,6 +482,9 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_password_config_file').with_args(
|
||||
'restorepass'
|
||||
).and_return('/dev/fd/99')
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@@ -468,8 +496,8 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'restoreport',
|
||||
'--username',
|
||||
'restoreuser',
|
||||
'--password',
|
||||
'restorepass',
|
||||
'--config',
|
||||
'/dev/fd/99',
|
||||
'--authenticationDatabase',
|
||||
'admin',
|
||||
],
|
||||
|
||||
@@ -9,7 +9,9 @@ from borgmatic.hooks.data_source import mysql as module
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, environment, dry_run=False)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'foo'}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
@@ -21,7 +23,9 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=True)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=True
|
||||
)
|
||||
|
||||
assert names == ()
|
||||
|
||||
@@ -31,12 +35,89 @@ def test_database_names_to_dump_queries_mysql_for_database_names():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, environment, dry_run=False)
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all'}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mysql_with_tls():
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all', 'tls': True}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mysql_without_tls():
|
||||
environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
'show schemas',
|
||||
),
|
||||
environment=environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump(
|
||||
{'name': 'all', 'tls': False}, {}, 'root', 'trustsome1', environment, dry_run=False
|
||||
)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@@ -56,6 +137,9 @@ def test_dump_data_sources_dumps_each_database():
|
||||
databases = [{'name': 'foo'}, {'name': 'bar'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
@@ -65,6 +149,8 @@ def test_dump_data_sources_dumps_each_database():
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
environment={'USER': 'root'},
|
||||
@@ -89,10 +175,10 @@ def test_dump_data_sources_dumps_with_password():
|
||||
database = {'name': 'foo', 'username': 'root', 'password': 'trustsome1'}
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@@ -100,9 +186,11 @@ def test_dump_data_sources_dumps_with_password():
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root'},
|
||||
dry_run=object,
|
||||
dry_run_label=object,
|
||||
).and_return(process).once()
|
||||
@@ -121,11 +209,16 @@ def test_dump_data_sources_dumps_all_databases_at_once():
|
||||
databases = [{'name': 'all'}]
|
||||
process = flexmock()
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': 'all'},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
environment={'USER': 'root'},
|
||||
@@ -147,6 +240,9 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
databases = [{'name': 'all', 'format': 'sql'}]
|
||||
processes = [flexmock(), flexmock()]
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo', 'bar'))
|
||||
|
||||
@@ -154,6 +250,8 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name, 'format': 'sql'},
|
||||
config={},
|
||||
username=None,
|
||||
password=None,
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
environment={'USER': 'root'},
|
||||
@@ -175,11 +273,18 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_mysql_with_list_options():
|
||||
database = {'name': 'all', 'list_options': '--defaults-extra-file=my.cnf'}
|
||||
database = {'name': 'all', 'list_options': '--defaults-extra-file=my.cnf --skip-ssl'}
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return(('--skip-ssl',), 'my.cnf')
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=my.cnf',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
@@ -188,20 +293,30 @@ def test_database_names_to_dump_runs_mysql_with_list_options():
|
||||
environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
database = {
|
||||
'name': 'all',
|
||||
'list_options': '--defaults-extra-file=my.cnf',
|
||||
'list_options': '--defaults-extra-file=my.cnf --skip-ssl',
|
||||
'mysql_command': 'custom_mysql',
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return(('--skip-ssl',), 'my.cnf')
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', 'my.cnf').and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
environment=None,
|
||||
full_command=(
|
||||
'custom_mysql', # Custom MySQL command
|
||||
'--defaults-extra-file=my.cnf',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--skip-ssl',
|
||||
'--skip-column-names',
|
||||
'--batch',
|
||||
'--execute',
|
||||
@@ -209,7 +324,10 @@ def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
),
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, 'root', 'trustsome1', None, '') == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mysqldump():
|
||||
@@ -219,11 +337,18 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
'foo',
|
||||
@@ -238,6 +363,8 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -255,11 +382,18 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
@@ -273,6 +407,8 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -290,11 +426,18 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--host',
|
||||
'database.example.org',
|
||||
@@ -315,6 +458,100 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
== process
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mysqldump_with_tls():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--ssl',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'tls': True},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
== process
|
||||
)
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mysqldump_without_tls():
|
||||
process = flexmock()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--skip-ssl',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment=None,
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'tls': False},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -332,20 +569,25 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--user',
|
||||
'root',
|
||||
'--databases',
|
||||
'foo',
|
||||
'--result-file',
|
||||
'dump',
|
||||
),
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={},
|
||||
run_to_completion=False,
|
||||
).and_return(process).once()
|
||||
|
||||
@@ -353,9 +595,11 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment={'MYSQL_PWD': 'trustsome1'},
|
||||
environment={},
|
||||
dry_run=False,
|
||||
dry_run_label='',
|
||||
)
|
||||
@@ -370,11 +614,18 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return(('--stuff=such',), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'mysqldump',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--stuff=such',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
@@ -390,6 +641,8 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -407,11 +660,18 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'custom_mysqldump', # Custom MySQL dump command
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--add-drop-database',
|
||||
'--databases',
|
||||
'foo',
|
||||
@@ -429,6 +689,8 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
'mysql_dump_command': 'custom_mysqldump',
|
||||
}, # Custom MySQL dump command specified
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -442,6 +704,12 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('dump')
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@@ -449,6 +717,8 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -465,6 +735,12 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@@ -473,6 +749,8 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
username='root',
|
||||
password='trustsome1',
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
environment=None,
|
||||
@@ -538,6 +816,12 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch'),
|
||||
@@ -570,9 +854,15 @@ def test_restore_data_source_dump_runs_mysql_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return(('--harder',), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--harder'),
|
||||
('mysql', '--harder', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
@@ -602,9 +892,15 @@ def test_restore_data_source_dump_runs_non_default_mysql_with_options():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return(('--harder',), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mysql', '--batch', '--harder'),
|
||||
('custom_mysql', '--harder', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
@@ -634,6 +930,12 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
@@ -668,6 +970,90 @@ def test_restore_data_source_dump_runs_mysql_with_hostname_and_port():
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mysql_with_tls():
|
||||
hook_config = [{'name': 'foo', 'tls': True}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--batch',
|
||||
'--ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
hook_config,
|
||||
{},
|
||||
data_source=hook_config[0],
|
||||
dry_run=False,
|
||||
extract_process=extract_process,
|
||||
connection_params={
|
||||
'hostname': None,
|
||||
'port': None,
|
||||
'username': None,
|
||||
'password': None,
|
||||
},
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mysql_without_tls():
|
||||
hook_config = [{'name': 'foo', 'tls': False}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--batch',
|
||||
'--skip-ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
hook_config,
|
||||
{},
|
||||
data_source=hook_config[0],
|
||||
dry_run=False,
|
||||
extract_process=extract_process,
|
||||
connection_params={
|
||||
'hostname': None,
|
||||
'port': None,
|
||||
'username': None,
|
||||
'password': None,
|
||||
},
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
)
|
||||
|
||||
|
||||
def test_restore_data_source_dump_runs_mysql_with_username_and_password():
|
||||
hook_config = [{'name': 'foo', 'username': 'root', 'password': 'trustsome1'}]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
@@ -675,13 +1061,19 @@ def test_restore_data_source_dump_runs_mysql_with_username_and_password():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('root', 'trustsome1', None).and_return(('--defaults-extra-file=/dev/fd/99',))
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--user', 'root'),
|
||||
('mysql', '--defaults-extra-file=/dev/fd/99', '--batch'),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'trustsome1'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -717,10 +1109,19 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('cliusername', 'clipassword', None).and_return(
|
||||
('--defaults-extra-file=/dev/fd/99',)
|
||||
)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--batch',
|
||||
'--host',
|
||||
'clihost',
|
||||
@@ -728,13 +1129,11 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
'cliport',
|
||||
'--protocol',
|
||||
'tcp',
|
||||
'--user',
|
||||
'cliusername',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'clipassword'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -761,10 +1160,12 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'password': 'trustsome1',
|
||||
'hostname': 'dbhost',
|
||||
'port': 'dbport',
|
||||
'tls': True,
|
||||
'restore_username': 'restoreuser',
|
||||
'restore_password': 'restorepass',
|
||||
'restore_hostname': 'restorehost',
|
||||
'restore_port': 'restoreport',
|
||||
'restore_tls': False,
|
||||
}
|
||||
]
|
||||
extract_process = flexmock(stdout=flexmock())
|
||||
@@ -772,10 +1173,19 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args('restoreuser', 'restorepass', None).and_return(
|
||||
('--defaults-extra-file=/dev/fd/99',)
|
||||
)
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
'--defaults-extra-file=/dev/fd/99',
|
||||
'--batch',
|
||||
'--host',
|
||||
'restorehost',
|
||||
@@ -783,13 +1193,12 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
'restoreport',
|
||||
'--protocol',
|
||||
'tcp',
|
||||
'--user',
|
||||
'restoreuser',
|
||||
'--skip-ssl',
|
||||
),
|
||||
processes=[extract_process],
|
||||
output_log_level=logging.DEBUG,
|
||||
input_file=extract_process.stdout,
|
||||
environment={'USER': 'root', 'MYSQL_PWD': 'restorepass'},
|
||||
environment={'USER': 'root'},
|
||||
).once()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@@ -814,6 +1223,12 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'parse_extra_options'
|
||||
).and_return((), None)
|
||||
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
|
||||
'make_defaults_file_options'
|
||||
).with_args(None, None, None).and_return(())
|
||||
flexmock(module.os).should_receive('environ').and_return({'USER': 'root'})
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
|
||||
@@ -555,6 +555,100 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
)
|
||||
|
||||
|
||||
def test_dump_data_sources_runs_pg_dump_with_string_compression():
|
||||
databases = [{'name': 'foo', 'compression': 'winrar'}]
|
||||
processes = [flexmock()]
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'pg_dump',
|
||||
'--no-password',
|
||||
'--clean',
|
||||
'--if-exists',
|
||||
'--format',
|
||||
'custom',
|
||||
'--compress',
|
||||
'winrar',
|
||||
'foo',
|
||||
'>',
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(processes[0]).once()
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
databases,
|
||||
{},
|
||||
config_paths=('test.yaml',),
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
patterns=[],
|
||||
dry_run=False,
|
||||
)
|
||||
== processes
|
||||
)
|
||||
|
||||
|
||||
def test_dump_data_sources_runs_pg_dump_with_integer_compression():
|
||||
databases = [{'name': 'foo', 'compression': 0}]
|
||||
processes = [flexmock()]
|
||||
flexmock(module).should_receive('make_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',))
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/foo'
|
||||
)
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
(
|
||||
'pg_dump',
|
||||
'--no-password',
|
||||
'--clean',
|
||||
'--if-exists',
|
||||
'--format',
|
||||
'custom',
|
||||
'--compress',
|
||||
'0',
|
||||
'foo',
|
||||
'>',
|
||||
'databases/localhost/foo',
|
||||
),
|
||||
shell=True,
|
||||
environment={'PGSSLMODE': 'disable'},
|
||||
run_to_completion=False,
|
||||
).and_return(processes[0]).once()
|
||||
|
||||
assert (
|
||||
module.dump_data_sources(
|
||||
databases,
|
||||
{},
|
||||
config_paths=('test.yaml',),
|
||||
borgmatic_runtime_directory='/run/borgmatic',
|
||||
patterns=[],
|
||||
dry_run=False,
|
||||
)
|
||||
== processes
|
||||
)
|
||||
|
||||
|
||||
def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
databases = [{'name': 'foo', 'options': '--stuff=such'}]
|
||||
process = flexmock()
|
||||
|
||||
@@ -220,11 +220,27 @@ def test_get_datasets_to_backup_with_invalid_list_output_raises():
|
||||
module.get_datasets_to_backup('zfs', patterns=(Pattern('/foo'), Pattern('/bar')))
|
||||
|
||||
|
||||
def test_get_all_dataset_mount_points_does_not_filter_datasets():
|
||||
def test_get_all_dataset_mount_points_omits_none():
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return(
|
||||
'/dataset\n/other',
|
||||
'/dataset\nnone\n/other',
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
).and_return((Pattern('/dataset'),))
|
||||
|
||||
assert module.get_all_dataset_mount_points('zfs') == (
|
||||
('/dataset'),
|
||||
('/other'),
|
||||
)
|
||||
|
||||
|
||||
def test_get_all_dataset_mount_points_omits_duplicates():
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).and_return(
|
||||
'/dataset\n/other\n/dataset\n/other',
|
||||
)
|
||||
flexmock(module.borgmatic.hooks.data_source.snapshot).should_receive(
|
||||
'get_contained_patterns'
|
||||
|
||||
@@ -10,7 +10,7 @@ CUSTOM_PUSH_URL = 'https://uptime.example.com/api/push/efgh5678'
|
||||
def test_ping_monitor_hits_default_uptimekuma_on_fail():
|
||||
hook_config = {}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{DEFAULT_PUSH_URL}?status=down&msg=fail'
|
||||
f'{DEFAULT_PUSH_URL}?status=down&msg=fail', verify=True
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -26,7 +26,7 @@ def test_ping_monitor_hits_default_uptimekuma_on_fail():
|
||||
def test_ping_monitor_hits_custom_uptimekuma_on_fail():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail'
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -42,7 +42,7 @@ def test_ping_monitor_hits_custom_uptimekuma_on_fail():
|
||||
def test_ping_monitor_custom_uptimekuma_on_start():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=up&msg=start'
|
||||
f'{CUSTOM_PUSH_URL}?status=up&msg=start', verify=True
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -58,7 +58,7 @@ def test_ping_monitor_custom_uptimekuma_on_start():
|
||||
def test_ping_monitor_custom_uptimekuma_on_finish():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=up&msg=finish'
|
||||
f'{CUSTOM_PUSH_URL}?status=up&msg=finish', verify=True
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -116,7 +116,7 @@ def test_ping_monitor_does_not_hit_custom_uptimekuma_on_finish_dry_run():
|
||||
def test_ping_monitor_with_connection_error_logs_warning():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail'
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
|
||||
).and_raise(module.requests.exceptions.ConnectionError)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
@@ -137,7 +137,7 @@ def test_ping_monitor_with_other_error_logs_warning():
|
||||
module.requests.exceptions.RequestException
|
||||
)
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail'
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
|
||||
).and_return(response)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
@@ -163,3 +163,35 @@ def test_ping_monitor_with_invalid_run_state():
|
||||
monitoring_log_level=1,
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_skips_ssl_verification_when_verify_tls_false():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': False}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=False
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_executes_ssl_verification_when_verify_tls_true():
|
||||
hook_config = {'push_url': CUSTOM_PUSH_URL, 'verify_tls': True}
|
||||
flexmock(module.requests).should_receive('get').with_args(
|
||||
f'{CUSTOM_PUSH_URL}?status=down&msg=fail', verify=True
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
@@ -24,7 +24,6 @@ DATA_HOST_KEY_WITH_KEY_VALUE = {
|
||||
'method': 'history.push',
|
||||
'params': {'host': HOST, 'key': KEY, 'value': VALUE},
|
||||
'id': 1,
|
||||
'auth': '3fe6ed01a69ebd79907a120bcd04e494',
|
||||
}
|
||||
|
||||
DATA_ITEMID = {
|
||||
@@ -39,7 +38,6 @@ DATA_HOST_KEY_WITH_ITEMID = {
|
||||
'method': 'history.push',
|
||||
'params': {'itemid': ITEMID, 'value': VALUE},
|
||||
'id': 1,
|
||||
'auth': '3fe6ed01a69ebd79907a120bcd04e494',
|
||||
}
|
||||
|
||||
DATA_USER_LOGIN = {
|
||||
@@ -49,17 +47,99 @@ DATA_USER_LOGIN = {
|
||||
'id': 1,
|
||||
}
|
||||
|
||||
AUTH_HEADERS_API_KEY = {
|
||||
DATA_USER_LOGOUT = {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'user.logout',
|
||||
'params': [],
|
||||
'id': 1,
|
||||
}
|
||||
|
||||
AUTH_HEADERS_LOGIN = {
|
||||
'Content-Type': 'application/json-rpc',
|
||||
}
|
||||
|
||||
AUTH_HEADERS = {
|
||||
'Content-Type': 'application/json-rpc',
|
||||
'Authorization': f'Bearer {API_KEY}',
|
||||
}
|
||||
|
||||
AUTH_HEADERS_USERNAME_PASSWORD = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
def test_send_zabbix_request_with_post_error_bails():
|
||||
server = flexmock()
|
||||
headers = flexmock()
|
||||
data = {'method': 'do.stuff'}
|
||||
response = flexmock(ok=False)
|
||||
response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.exceptions.RequestException
|
||||
)
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
server, headers=headers, json=data
|
||||
).and_return(response)
|
||||
|
||||
assert module.send_zabbix_request(server, headers, data) is None
|
||||
|
||||
|
||||
def test_send_zabbix_request_with_invalid_json_response_bails():
|
||||
server = flexmock()
|
||||
headers = flexmock()
|
||||
data = {'method': 'do.stuff'}
|
||||
flexmock(module.requests.exceptions.JSONDecodeError).should_receive('__init__')
|
||||
response = flexmock(ok=True)
|
||||
response.should_receive('json').and_raise(module.requests.exceptions.JSONDecodeError)
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
server, headers=headers, json=data
|
||||
).and_return(response)
|
||||
|
||||
assert module.send_zabbix_request(server, headers, data) is None
|
||||
|
||||
|
||||
def test_send_zabbix_request_with_success_returns_response_result():
|
||||
server = flexmock()
|
||||
headers = flexmock()
|
||||
data = {'method': 'do.stuff'}
|
||||
response = flexmock(ok=True)
|
||||
response.should_receive('json').and_return({'result': {'foo': 'bar'}})
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
server, headers=headers, json=data
|
||||
).and_return(response)
|
||||
|
||||
assert module.send_zabbix_request(server, headers, data) == {'foo': 'bar'}
|
||||
|
||||
|
||||
def test_send_zabbix_request_with_success_passes_through_missing_result():
|
||||
server = flexmock()
|
||||
headers = flexmock()
|
||||
data = {'method': 'do.stuff'}
|
||||
response = flexmock(ok=True)
|
||||
response.should_receive('json').and_return({})
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
server, headers=headers, json=data
|
||||
).and_return(response)
|
||||
|
||||
assert module.send_zabbix_request(server, headers, data) is None
|
||||
|
||||
|
||||
def test_send_zabbix_request_with_error_bails():
|
||||
server = flexmock()
|
||||
headers = flexmock()
|
||||
data = {'method': 'do.stuff'}
|
||||
response = flexmock(ok=True)
|
||||
response.should_receive('json').and_return({'result': {'data': [{'error': 'oops'}]}})
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
server, headers=headers, json=data
|
||||
).and_return(response)
|
||||
|
||||
assert module.send_zabbix_request(server, headers, data) is None
|
||||
|
||||
|
||||
def test_ping_monitor_with_non_matching_state_bails():
|
||||
hook_config = {'api_key': API_KEY}
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -79,7 +159,7 @@ def test_ping_monitor_config_with_api_key_only_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -99,7 +179,7 @@ def test_ping_monitor_config_with_host_only_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -119,7 +199,7 @@ def test_ping_monitor_config_with_key_only_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -139,7 +219,7 @@ def test_ping_monitor_config_with_server_only_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -158,7 +238,7 @@ def test_ping_monitor_config_user_password_no_zabbix_data_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -177,7 +257,7 @@ def test_ping_monitor_config_api_key_no_zabbix_data_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -197,7 +277,7 @@ def test_ping_monitor_config_itemid_no_auth_data_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -217,7 +297,7 @@ def test_ping_monitor_config_host_and_key_no_auth_data_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -236,11 +316,35 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
json=DATA_HOST_KEY,
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_HOST_KEY,
|
||||
).once()
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_adds_missing_api_endpoint_to_server_url():
|
||||
# This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
|
||||
# to authenticate and HOST/KEY to know which item to populate in Zabbix.
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_HOST_KEY,
|
||||
).once()
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -259,7 +363,7 @@ def test_ping_monitor_config_host_and_missing_key_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -277,7 +381,7 @@ def test_ping_monitor_config_key_and_missing_host_bails():
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -303,24 +407,26 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
)
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_USER_LOGIN,
|
||||
).and_return(auth_response).once()
|
||||
headers=AUTH_HEADERS_LOGIN,
|
||||
data=DATA_USER_LOGIN,
|
||||
).and_return('fakekey').once()
|
||||
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_HOST_KEY_WITH_KEY_VALUE,
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_HOST_KEY_WITH_KEY_VALUE,
|
||||
).once()
|
||||
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_USER_LOGOUT,
|
||||
).once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -344,24 +450,22 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_a
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
auth_response = flexmock(ok=False)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
)
|
||||
auth_response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.ConnectionError
|
||||
).once()
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_USER_LOGIN,
|
||||
).and_return(auth_response).once()
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
headers=AUTH_HEADERS_LOGIN,
|
||||
data=DATA_USER_LOGIN,
|
||||
).and_return(None).once()
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_HOST_KEY_WITH_KEY_VALUE,
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_HOST_KEY_WITH_KEY_VALUE,
|
||||
).never()
|
||||
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_USER_LOGOUT,
|
||||
).never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -386,7 +490,7 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bai
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -410,7 +514,7 @@ def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bai
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -429,11 +533,11 @@ def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
json=DATA_ITEMID,
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_ITEMID,
|
||||
).once()
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@@ -454,63 +558,26 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
)
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_USER_LOGIN,
|
||||
).and_return(auth_response).once()
|
||||
headers=AUTH_HEADERS_LOGIN,
|
||||
data=DATA_USER_LOGIN,
|
||||
).and_return('fakekey').once()
|
||||
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_HOST_KEY_WITH_ITEMID,
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
{},
|
||||
'config.yaml',
|
||||
borgmatic.hooks.monitoring.monitor.State.FAIL,
|
||||
monitoring_log_level=1,
|
||||
dry_run=False,
|
||||
)
|
||||
|
||||
|
||||
def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_post_error_bails():
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value, config: value)
|
||||
auth_response = flexmock(ok=True)
|
||||
auth_response.should_receive('json').and_return(
|
||||
{'jsonrpc': '2.0', 'result': '3fe6ed01a69ebd79907a120bcd04e494', 'id': 1}
|
||||
)
|
||||
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_USER_LOGIN,
|
||||
).and_return(auth_response).once()
|
||||
|
||||
push_response = flexmock(ok=False)
|
||||
push_response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.ConnectionError
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_HOST_KEY_WITH_ITEMID,
|
||||
).once()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_USERNAME_PASSWORD,
|
||||
json=DATA_HOST_KEY_WITH_ITEMID,
|
||||
).and_return(push_response).once()
|
||||
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module).should_receive('send_zabbix_request').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS,
|
||||
data=DATA_USER_LOGOUT,
|
||||
).once()
|
||||
|
||||
module.ping_monitor(
|
||||
hook_config,
|
||||
@@ -528,7 +595,7 @@ def test_ping_monitor_with_credential_error_bails():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).and_raise(ValueError)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
flexmock(module).should_receive('send_zabbix_request').never()
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
|
||||
module.ping_monitor(
|
||||
|
||||
+31
-93
@@ -191,7 +191,7 @@ def test_execute_command_calls_full_command():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -213,7 +213,7 @@ def test_execute_command_calls_full_command_with_output_file():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stderr=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -234,7 +234,7 @@ def test_execute_command_calls_full_command_without_capturing_output():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(wait=lambda: 0)).once()
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
@@ -257,7 +257,7 @@ def test_execute_command_calls_full_command_with_input_file():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -278,7 +278,7 @@ def test_execute_command_calls_full_command_with_shell():
|
||||
shell=True,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -299,7 +299,7 @@ def test_execute_command_calls_full_command_with_environment():
|
||||
shell=False,
|
||||
env={'a': 'b'},
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -320,33 +320,12 @@ def test_execute_command_calls_full_command_with_working_directory():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd='/working',
|
||||
close_fds=True,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
|
||||
output = module.execute_command(full_command, working_directory='/working')
|
||||
|
||||
assert output is None
|
||||
|
||||
|
||||
def test_execute_command_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
|
||||
full_command = ['foo', 'bar']
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('Popen').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stdout=module.subprocess.PIPE,
|
||||
stderr=module.subprocess.STDOUT,
|
||||
shell=False,
|
||||
env={'BORG_PASSPHRASE_FD': '4'},
|
||||
cwd=None,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
|
||||
output = module.execute_command(full_command, environment={'BORG_PASSPHRASE_FD': '4'})
|
||||
output = module.execute_command(full_command, working_directory='/working')
|
||||
|
||||
assert output is None
|
||||
|
||||
@@ -363,7 +342,7 @@ def test_execute_command_without_run_to_completion_returns_process():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(process).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -377,11 +356,12 @@ def test_execute_command_and_capture_output_returns_stdout():
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(full_command)
|
||||
@@ -395,11 +375,12 @@ def test_execute_command_and_capture_output_with_capture_stderr_returns_stderr()
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=module.subprocess.STDOUT,
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(full_command, capture_stderr=True)
|
||||
@@ -414,11 +395,12 @@ def test_execute_command_and_capture_output_returns_output_when_process_error_is
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_raise(subprocess.CalledProcessError(1, full_command, err_output)).once()
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(
|
||||
module.Exit_status.SUCCESS
|
||||
@@ -435,11 +417,12 @@ def test_execute_command_and_capture_output_raises_when_command_errors():
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_raise(subprocess.CalledProcessError(2, full_command, expected_output)).once()
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(
|
||||
module.Exit_status.ERROR
|
||||
@@ -455,11 +438,12 @@ def test_execute_command_and_capture_output_returns_output_with_shell():
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
'foo bar',
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=True,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(full_command, shell=True)
|
||||
@@ -473,11 +457,12 @@ def test_execute_command_and_capture_output_returns_output_with_environment():
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env={'a': 'b'},
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(
|
||||
@@ -493,37 +478,16 @@ def test_execute_command_and_capture_output_returns_output_with_working_director
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd='/working',
|
||||
close_fds=True,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(
|
||||
full_command, shell=False, working_directory='/working'
|
||||
)
|
||||
|
||||
assert output == expected_output
|
||||
|
||||
|
||||
def test_execute_command_and_capture_output_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
|
||||
full_command = ['foo', 'bar']
|
||||
expected_output = '[]'
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('check_output').with_args(
|
||||
full_command,
|
||||
stderr=None,
|
||||
shell=False,
|
||||
env={'BORG_PASSPHRASE_FD': '4'},
|
||||
cwd=None,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(decode=lambda: expected_output)).once()
|
||||
|
||||
output = module.execute_command_and_capture_output(
|
||||
full_command,
|
||||
shell=False,
|
||||
environment={'BORG_PASSPHRASE_FD': '4'},
|
||||
full_command, shell=False, working_directory='/working'
|
||||
)
|
||||
|
||||
assert output == expected_output
|
||||
@@ -541,7 +505,7 @@ def test_execute_command_with_processes_calls_full_command():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -564,7 +528,7 @@ def test_execute_command_with_processes_returns_output_with_output_log_level_non
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(process).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs').and_return({process: 'out'})
|
||||
@@ -587,7 +551,7 @@ def test_execute_command_with_processes_calls_full_command_with_output_file():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stderr=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -609,7 +573,7 @@ def test_execute_command_with_processes_calls_full_command_without_capturing_out
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(wait=lambda: 0)).once()
|
||||
flexmock(module).should_receive('interpret_exit_code').and_return(module.Exit_status.SUCCESS)
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
@@ -635,7 +599,7 @@ def test_execute_command_with_processes_calls_full_command_with_input_file():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -657,7 +621,7 @@ def test_execute_command_with_processes_calls_full_command_with_shell():
|
||||
shell=True,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -679,7 +643,7 @@ def test_execute_command_with_processes_calls_full_command_with_environment():
|
||||
shell=False,
|
||||
env={'a': 'b'},
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
@@ -701,39 +665,13 @@ def test_execute_command_with_processes_calls_full_command_with_working_director
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd='/working',
|
||||
close_fds=True,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
|
||||
output = module.execute_command_with_processes(
|
||||
full_command, processes, working_directory='/working'
|
||||
)
|
||||
|
||||
assert output is None
|
||||
|
||||
|
||||
def test_execute_command_with_processes_with_BORG_PASSPHRASE_FD_leaves_file_descriptors_open():
|
||||
full_command = ['foo', 'bar']
|
||||
processes = (flexmock(),)
|
||||
flexmock(module).should_receive('log_command')
|
||||
flexmock(module.subprocess).should_receive('Popen').with_args(
|
||||
full_command,
|
||||
stdin=None,
|
||||
stdout=module.subprocess.PIPE,
|
||||
stderr=module.subprocess.STDOUT,
|
||||
shell=False,
|
||||
env={'BORG_PASSPHRASE_FD': '4'},
|
||||
cwd=None,
|
||||
close_fds=False,
|
||||
).and_return(flexmock(stdout=None)).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs')
|
||||
|
||||
output = module.execute_command_with_processes(
|
||||
full_command,
|
||||
processes,
|
||||
environment={'BORG_PASSPHRASE_FD': '4'},
|
||||
full_command, processes, working_directory='/working'
|
||||
)
|
||||
|
||||
assert output is None
|
||||
@@ -754,7 +692,7 @@ def test_execute_command_with_processes_kills_processes_on_error():
|
||||
shell=False,
|
||||
env=None,
|
||||
cwd=None,
|
||||
close_fds=True,
|
||||
close_fds=False,
|
||||
).and_raise(subprocess.CalledProcessError(1, full_command, 'error')).once()
|
||||
flexmock(module.borgmatic.logger).should_receive('Log_prefix').and_return(flexmock())
|
||||
flexmock(module).should_receive('log_outputs').never()
|
||||
|
||||
@@ -26,7 +26,7 @@ def test_handle_signal_bails_on_recursion():
|
||||
def test_handle_signal_exits_on_sigterm():
|
||||
signal_number = module.signal.SIGTERM
|
||||
frame = flexmock(f_back=flexmock(f_code=flexmock(co_name='something')))
|
||||
flexmock(module.os).should_receive('getpgrp').and_return(flexmock)
|
||||
flexmock(module.os).should_receive('getpgrp').and_return(flexmock())
|
||||
flexmock(module.os).should_receive('killpg')
|
||||
flexmock(module.sys).should_receive('exit').with_args(
|
||||
module.EXIT_CODE_FROM_SIGNAL + signal_number
|
||||
@@ -38,8 +38,10 @@ def test_handle_signal_exits_on_sigterm():
|
||||
def test_handle_signal_raises_on_sigint():
|
||||
signal_number = module.signal.SIGINT
|
||||
frame = flexmock(f_back=flexmock(f_code=flexmock(co_name='something')))
|
||||
flexmock(module.os).should_receive('getpgrp').and_return(flexmock)
|
||||
flexmock(module.os).should_receive('killpg')
|
||||
process_group = flexmock()
|
||||
flexmock(module.os).should_receive('getpgrp').and_return(process_group)
|
||||
flexmock(module.os).should_receive('killpg').with_args(process_group, module.signal.SIGINT)
|
||||
flexmock(module.os).should_receive('killpg').with_args(process_group, module.signal.SIGTERM)
|
||||
flexmock(module.sys).should_receive('exit').never()
|
||||
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
|
||||
Reference in New Issue
Block a user