Add credential loading from file, KeePassXC, and Docker/Podman secrets.
Reviewed-on: #994
This commit is contained in:
commit
7f22612bf1
2
NEWS
2
NEWS
@ -1,6 +1,8 @@
|
||||
1.9.11.dev0
|
||||
* #996: Fix the "create" action to omit the repository label prefix from Borg's output when
|
||||
databases are enabled.
|
||||
* Add credential loading from file, KeePassXC, and Docker/Podman secrets. See the documentation for
|
||||
more information: https://torsion.org/borgmatic/docs/how-to/provide-your-passwords/
|
||||
|
||||
1.9.10
|
||||
* #966: Add a "{credential ...}" syntax for loading systemd credentials into borgmatic
|
||||
|
@ -88,6 +88,9 @@ borgmatic is powered by [Borg Backup](https://www.borgbackup.org/).
|
||||
### Credentials
|
||||
|
||||
<a href="https://systemd.io/"><img src="docs/static/systemd.png" alt="Sentry" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://www.docker.com/"><img src="docs/static/docker.png" alt="Docker" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://podman.io/"><img src="docs/static/podman.png" alt="Podman" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
<a href="https://keepassxc.org/"><img src="docs/static/keepassxc.png" alt="Podman" height="40px" style="margin-bottom:20px; margin-right:20px;"></a>
|
||||
|
||||
|
||||
## Getting started
|
||||
|
@ -41,7 +41,7 @@ def make_environment(config):
|
||||
value = config.get(option_name)
|
||||
|
||||
if option_name in CREDENTIAL_OPTIONS and value is not None:
|
||||
value = borgmatic.hooks.credential.parse.resolve_credential(value)
|
||||
value = borgmatic.hooks.credential.parse.resolve_credential(value, config)
|
||||
|
||||
if value is not None:
|
||||
environment[environment_variable_name] = str(value)
|
||||
|
@ -2402,3 +2402,25 @@ properties:
|
||||
description: |
|
||||
Configuration for integration with Linux LVM (Logical Volume
|
||||
Manager).
|
||||
container:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
secrets_directory:
|
||||
type: string
|
||||
description: |
|
||||
Secrets directory to use instead of "/run/secrets".
|
||||
example: /path/to/secrets
|
||||
description: |
|
||||
Configuration for integration with Docker or Podman secrets.
|
||||
keepassxc:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
properties:
|
||||
keepassxc_cli_command:
|
||||
type: string
|
||||
description: |
|
||||
Command to use instead of "keepassxc-cli".
|
||||
example: /usr/local/bin/keepassxc-cli
|
||||
description: |
|
||||
Configuration for integration with the KeePassXC password manager.
|
||||
|
41
borgmatic/hooks/credential/container.py
Normal file
41
borgmatic/hooks/credential/container.py
Normal file
@ -0,0 +1,41 @@
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SECRET_NAME_PATTERN = re.compile(r'^\w+$')
|
||||
DEFAULT_SECRETS_DIRECTORY = '/run/secrets'
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a secret name to load, read the secret from the corresponding container secrets file
|
||||
and return it.
|
||||
|
||||
Raise ValueError if the credential parameters is not one element, the secret name is invalid, or
|
||||
the secret file cannot be read.
|
||||
'''
|
||||
try:
|
||||
(secret_name,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(f'Cannot load invalid secret name: "{' '.join(credential_parameters)}"')
|
||||
|
||||
if not SECRET_NAME_PATTERN.match(secret_name):
|
||||
raise ValueError(f'Cannot load invalid secret name: "{secret_name}"')
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(
|
||||
config.get('working_directory', ''),
|
||||
(hook_config or {}).get('secrets_directory', DEFAULT_SECRETS_DIRECTORY),
|
||||
secret_name,
|
||||
)
|
||||
) as secret_file:
|
||||
return secret_file.read().rstrip(os.linesep)
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
logger.warning(error)
|
||||
|
||||
raise ValueError(f'Cannot load secret "{secret_name}" from file: {error.filename}')
|
28
borgmatic/hooks/credential/file.py
Normal file
28
borgmatic/hooks/credential/file.py
Normal file
@ -0,0 +1,28 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a credential path to load, load the credential from file and return it.
|
||||
|
||||
Raise ValueError if the credential parameters is not one element or the secret file cannot be
|
||||
read.
|
||||
'''
|
||||
try:
|
||||
(credential_path,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(f'Cannot load invalid credential: "{' '.join(credential_parameters)}"')
|
||||
|
||||
try:
|
||||
with open(
|
||||
os.path.join(config.get('working_directory', ''), credential_path)
|
||||
) as credential_file:
|
||||
return credential_file.read().rstrip(os.linesep)
|
||||
except (FileNotFoundError, OSError) as error:
|
||||
logger.warning(error)
|
||||
|
||||
raise ValueError(f'Cannot load credential file: {error.filename}')
|
40
borgmatic/hooks/credential/keepassxc.py
Normal file
40
borgmatic/hooks/credential/keepassxc.py
Normal file
@ -0,0 +1,40 @@
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
|
||||
import borgmatic.execute
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a KeePassXC database path and an attribute name to load, run keepassxc-cli to fetch
|
||||
the corresponidng KeePassXC credential and return it.
|
||||
|
||||
Raise ValueError if keepassxc-cli can't retrieve the credential.
|
||||
'''
|
||||
try:
|
||||
(database_path, attribute_name) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f'Cannot load credential with invalid KeePassXC database path and attribute name: "{' '.join(credential_parameters)}"'
|
||||
)
|
||||
|
||||
if not os.path.exists(database_path):
|
||||
raise ValueError(
|
||||
f'Cannot load credential because KeePassXC database path does not exist: {database_path}'
|
||||
)
|
||||
|
||||
return borgmatic.execute.execute_command_and_capture_output(
|
||||
tuple(shlex.split((hook_config or {}).get('keepassxc_cli_command', 'keepassxc-cli')))
|
||||
+ (
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
database_path,
|
||||
attribute_name,
|
||||
)
|
||||
).rstrip(os.linesep)
|
@ -1,42 +1,124 @@
|
||||
import functools
|
||||
import re
|
||||
import shlex
|
||||
|
||||
import borgmatic.hooks.dispatch
|
||||
|
||||
IS_A_HOOK = False
|
||||
|
||||
|
||||
CREDENTIAL_PATTERN = re.compile(
|
||||
r'\{credential +(?P<hook_name>[A-Za-z0-9_]+) +(?P<credential_name>[A-Za-z0-9_]+)\}'
|
||||
)
|
||||
|
||||
GENERAL_CREDENTIAL_PATTERN = re.compile(r'\{credential( +[^}]*)?\}')
|
||||
|
||||
|
||||
@functools.cache
|
||||
def resolve_credential(value):
|
||||
class Hash_adapter:
|
||||
'''
|
||||
Given a configuration value containing a string like "{credential hookname credentialname}", resolve it by
|
||||
calling the relevant hook to get the actual credential value. If the given value does not
|
||||
actually contain a credential tag, then return it unchanged.
|
||||
A Hash_adapter instance wraps an unhashable object and pretends it's hashable. This is intended
|
||||
for passing to a @functools.cache-decorated function to prevent it from complaining that an
|
||||
argument is unhashable. It should only be used for arguments that you don't want to actually
|
||||
impact the cache hashing, because Hash_adapter doesn't actually hash the object's contents.
|
||||
|
||||
Cache the value so repeated calls to this function don't need to load the credential repeatedly.
|
||||
Example usage:
|
||||
|
||||
@functools.cache
|
||||
def func(a, b):
|
||||
print(a, b.actual_value)
|
||||
return a
|
||||
|
||||
func(5, Hash_adapter({1: 2, 3: 4})) # Calls func(), prints, and returns.
|
||||
func(5, Hash_adapter({1: 2, 3: 4})) # Hits the cache and just returns the value.
|
||||
func(5, Hash_adapter({5: 6, 7: 8})) # Also uses cache, since the Hash_adapter is ignored.
|
||||
|
||||
In the above function, the "b" value is one that has been wrapped with Hash_adappter, and
|
||||
therefore "b.actual_value" is necessary to access the original value.
|
||||
'''
|
||||
|
||||
def __init__(self, actual_value):
|
||||
self.actual_value = actual_value
|
||||
|
||||
def __eq__(self, other):
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return 0
|
||||
|
||||
|
||||
UNHASHABLE_TYPES = (dict, list, set)
|
||||
|
||||
|
||||
def cache_ignoring_unhashable_arguments(function):
|
||||
'''
|
||||
A function decorator that caches calls to the decorated function but ignores any unhashable
|
||||
arguments when performing cache lookups. This is intended to be a drop-in replacement for
|
||||
functools.cache.
|
||||
|
||||
Example usage:
|
||||
|
||||
@cache_ignoring_unhashable_arguments
|
||||
def func(a, b):
|
||||
print(a, b)
|
||||
return a
|
||||
|
||||
func(5, {1: 2, 3: 4}) # Calls func(), prints, and returns.
|
||||
func(5, {1: 2, 3: 4}) # Hits the cache and just returns the value.
|
||||
func(5, {5: 6, 7: 8}) # Also uses cache, since the unhashable value (the dict) is ignored.
|
||||
'''
|
||||
|
||||
@functools.cache
|
||||
def cached_function(*args, **kwargs):
|
||||
return function(
|
||||
*(arg.actual_value if isinstance(arg, Hash_adapter) else arg for arg in args),
|
||||
**{
|
||||
key: value.actual_value if isinstance(value, Hash_adapter) else value
|
||||
for (key, value) in kwargs.items()
|
||||
},
|
||||
)
|
||||
|
||||
@functools.wraps(function)
|
||||
def wrapper_function(*args, **kwargs):
|
||||
return cached_function(
|
||||
*(Hash_adapter(arg) if isinstance(arg, UNHASHABLE_TYPES) else arg for arg in args),
|
||||
**{
|
||||
key: Hash_adapter(value) if isinstance(value, UNHASHABLE_TYPES) else value
|
||||
for (key, value) in kwargs.items()
|
||||
},
|
||||
)
|
||||
|
||||
wrapper_function.cache_clear = cached_function.cache_clear
|
||||
|
||||
return wrapper_function
|
||||
|
||||
|
||||
CREDENTIAL_PATTERN = re.compile(r'\{credential( +(?P<hook_and_parameters>.*))?\}')
|
||||
|
||||
|
||||
@cache_ignoring_unhashable_arguments
|
||||
def resolve_credential(value, config):
|
||||
'''
|
||||
Given a configuration value containing a string like "{credential hookname credentialname}" and
|
||||
a configuration dict, resolve the credential by calling the relevant hook to get the actual
|
||||
credential value. If the given value does not actually contain a credential tag, then return it
|
||||
unchanged.
|
||||
|
||||
Cache the value (ignoring the config for purposes of caching), so repeated calls to this
|
||||
function don't need to load the credential repeatedly.
|
||||
|
||||
Raise ValueError if the config could not be parsed or the credential could not be loaded.
|
||||
'''
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
result = CREDENTIAL_PATTERN.sub(
|
||||
lambda matcher: borgmatic.hooks.dispatch.call_hook(
|
||||
'load_credential', {}, matcher.group('hook_name'), matcher.group('credential_name')
|
||||
),
|
||||
value,
|
||||
)
|
||||
matcher = CREDENTIAL_PATTERN.match(value)
|
||||
|
||||
# If we've tried to parse the credential, but the parsed result still looks kind of like a
|
||||
# credential, it means it's invalid syntax.
|
||||
if GENERAL_CREDENTIAL_PATTERN.match(result):
|
||||
if not matcher:
|
||||
return value
|
||||
|
||||
hook_and_parameters = matcher.group('hook_and_parameters')
|
||||
|
||||
if not hook_and_parameters:
|
||||
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
|
||||
|
||||
return result
|
||||
(hook_name, *credential_parameters) = shlex.split(hook_and_parameters)
|
||||
|
||||
if not credential_parameters:
|
||||
raise ValueError(f'Cannot load credential with invalid syntax "{value}"')
|
||||
|
||||
return borgmatic.hooks.dispatch.call_hook(
|
||||
'load_credential', config, hook_name, tuple(credential_parameters)
|
||||
)
|
||||
|
@ -8,14 +8,22 @@ logger = logging.getLogger(__name__)
|
||||
CREDENTIAL_NAME_PATTERN = re.compile(r'^\w+$')
|
||||
|
||||
|
||||
def load_credential(hook_config, config, credential_name):
|
||||
def load_credential(hook_config, config, credential_parameters):
|
||||
'''
|
||||
Given the hook configuration dict, the configuration dict, and a credential name to load, read
|
||||
the credential from the corresponding systemd credential file and return it.
|
||||
Given the hook configuration dict, the configuration dict, and a credential parameters tuple
|
||||
containing a credential name to load, read the credential from the corresponding systemd
|
||||
credential file and return it.
|
||||
|
||||
Raise ValueError if the systemd CREDENTIALS_DIRECTORY environment variable is not set, the
|
||||
credential name is invalid, or the credential file cannot be read.
|
||||
'''
|
||||
try:
|
||||
(credential_name,) = credential_parameters
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f'Cannot load invalid credential name: "{' '.join(credential_parameters)}"'
|
||||
)
|
||||
|
||||
credentials_directory = os.environ.get('CREDENTIALS_DIRECTORY')
|
||||
|
||||
if not credentials_directory:
|
||||
|
@ -26,11 +26,11 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, extra_environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, 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 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.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--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']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@ -67,7 +70,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
database, config, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MariaDB database (provided as a configuration dict) to a named
|
||||
@ -102,7 +105,10 @@ def execute_dump_command(
|
||||
+ (('--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']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@ -162,11 +168,15 @@ def dump_data_sources(
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else None
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, extra_environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@ -181,6 +191,7 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
extra_environment,
|
||||
@ -192,6 +203,7 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
extra_environment,
|
||||
@ -265,12 +277,18 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
mariadb_restore_command = tuple(
|
||||
|
@ -69,7 +69,7 @@ def dump_data_sources(
|
||||
if dry_run:
|
||||
continue
|
||||
|
||||
command = build_dump_command(database, dump_filename, dump_format)
|
||||
command = build_dump_command(database, config, dump_filename, dump_format)
|
||||
|
||||
if dump_format == 'directory':
|
||||
dump.create_parent_directory_for_dump(dump_filename)
|
||||
@ -88,7 +88,7 @@ def dump_data_sources(
|
||||
return processes
|
||||
|
||||
|
||||
def build_dump_command(database, dump_filename, dump_format):
|
||||
def build_dump_command(database, config, dump_filename, dump_format):
|
||||
'''
|
||||
Return the mongodump command from a single database configuration.
|
||||
'''
|
||||
@ -103,7 +103,9 @@ def build_dump_command(database, dump_filename, dump_format):
|
||||
(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'])
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['username'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'username' in database
|
||||
@ -113,7 +115,9 @@ def build_dump_command(database, dump_filename, dump_format):
|
||||
(
|
||||
'--password',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['password'])
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
if 'password' in database
|
||||
@ -192,7 +196,7 @@ def restore_data_source_dump(
|
||||
data_source.get('hostname'),
|
||||
)
|
||||
restore_command = build_restore_command(
|
||||
extract_process, data_source, dump_filename, connection_params
|
||||
extract_process, data_source, config, dump_filename, connection_params
|
||||
)
|
||||
|
||||
logger.debug(f"Restoring MongoDB database {data_source['name']}{dry_run_label}")
|
||||
@ -209,7 +213,7 @@ def restore_data_source_dump(
|
||||
)
|
||||
|
||||
|
||||
def build_restore_command(extract_process, database, dump_filename, connection_params):
|
||||
def build_restore_command(extract_process, database, config, dump_filename, connection_params):
|
||||
'''
|
||||
Return the mongorestore command from a single database configuration.
|
||||
'''
|
||||
@ -218,10 +222,18 @@ def build_restore_command(extract_process, database, dump_filename, connection_p
|
||||
)
|
||||
port = str(connection_params['port'] or database.get('restore_port', database.get('port', '')))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username'] or database.get('restore_username', database.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or database.get('restore_username', database.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password'] or database.get('restore_password', database.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or database.get('restore_password', database.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
command = ['mongorestore']
|
||||
|
@ -26,11 +26,11 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
SYSTEM_DATABASE_NAMES = ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, extra_environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, 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 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.
|
||||
'''
|
||||
if database['name'] != 'all':
|
||||
return (database['name'],)
|
||||
@ -47,7 +47,10 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (('--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']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@ -67,7 +70,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
|
||||
|
||||
def execute_dump_command(
|
||||
database, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
database, config, dump_path, database_names, extra_environment, dry_run, dry_run_label
|
||||
):
|
||||
'''
|
||||
Kick off a dump for the given MySQL/MariaDB database (provided as a configuration dict) to a
|
||||
@ -101,7 +104,10 @@ def execute_dump_command(
|
||||
+ (('--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']))
|
||||
(
|
||||
'--user',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
)
|
||||
@ -161,11 +167,15 @@ def dump_data_sources(
|
||||
for database in databases:
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
extra_environment = (
|
||||
{'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(database['password'])}
|
||||
{
|
||||
'MYSQL_PWD': borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password'], config
|
||||
)
|
||||
}
|
||||
if 'password' in database
|
||||
else None
|
||||
)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, extra_environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@ -180,6 +190,7 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
renamed_database,
|
||||
config,
|
||||
dump_path,
|
||||
(dump_name,),
|
||||
extra_environment,
|
||||
@ -191,6 +202,7 @@ def dump_data_sources(
|
||||
processes.append(
|
||||
execute_dump_command(
|
||||
database,
|
||||
config,
|
||||
dump_path,
|
||||
dump_database_names,
|
||||
extra_environment,
|
||||
@ -264,12 +276,18 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
(
|
||||
connection_params['password']
|
||||
or data_source.get('restore_password', data_source.get('password'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
mysql_restore_command = tuple(
|
||||
|
@ -25,7 +25,7 @@ def make_dump_path(base_directory): # pragma: no cover
|
||||
return dump.make_data_source_dump_path(base_directory, 'postgresql_databases')
|
||||
|
||||
|
||||
def make_extra_environment(database, restore_connection_params=None):
|
||||
def make_extra_environment(database, config, restore_connection_params=None):
|
||||
'''
|
||||
Make the extra_environment dict from the given database configuration. If restore connection
|
||||
params are given, this is for a restore operation.
|
||||
@ -35,12 +35,15 @@ def make_extra_environment(database, restore_connection_params=None):
|
||||
try:
|
||||
if restore_connection_params:
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
restore_connection_params.get('password')
|
||||
or database.get('restore_password', database['password'])
|
||||
(
|
||||
restore_connection_params.get('password')
|
||||
or database.get('restore_password', database['password'])
|
||||
),
|
||||
config,
|
||||
)
|
||||
else:
|
||||
extra['PGPASSWORD'] = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['password']
|
||||
database['password'], config
|
||||
)
|
||||
except (AttributeError, KeyError):
|
||||
pass
|
||||
@ -62,12 +65,12 @@ def make_extra_environment(database, restore_connection_params=None):
|
||||
EXCLUDED_DATABASE_NAMES = ('template0', 'template1')
|
||||
|
||||
|
||||
def database_names_to_dump(database, extra_environment, dry_run):
|
||||
def database_names_to_dump(database, config, extra_environment, dry_run):
|
||||
'''
|
||||
Given a requested database config, return the corresponding sequence of database names to dump.
|
||||
In the case of "all" when a database format is given, query for the names of databases on the
|
||||
configured host and return them. For "all" without a database format, just return a sequence
|
||||
containing "all".
|
||||
Given a requested database config and a configuration dict, return the corresponding sequence of
|
||||
database names to dump. In the case of "all" when a database format is given, query for the
|
||||
names of databases on the configured host and return them. For "all" without a database format,
|
||||
just return a sequence containing "all".
|
||||
'''
|
||||
requested_name = database['name']
|
||||
|
||||
@ -89,7 +92,7 @@ def database_names_to_dump(database, extra_environment, dry_run):
|
||||
+ (
|
||||
(
|
||||
'--username',
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username']),
|
||||
borgmatic.hooks.credential.parse.resolve_credential(database['username'], config),
|
||||
)
|
||||
if 'username' in database
|
||||
else ()
|
||||
@ -146,9 +149,9 @@ def dump_data_sources(
|
||||
logger.info(f'Dumping PostgreSQL databases{dry_run_label}')
|
||||
|
||||
for database in databases:
|
||||
extra_environment = make_extra_environment(database)
|
||||
extra_environment = make_extra_environment(database, config)
|
||||
dump_path = make_dump_path(borgmatic_runtime_directory)
|
||||
dump_database_names = database_names_to_dump(database, extra_environment, dry_run)
|
||||
dump_database_names = database_names_to_dump(database, config, extra_environment, dry_run)
|
||||
|
||||
if not dump_database_names:
|
||||
if dry_run:
|
||||
@ -189,7 +192,7 @@ def dump_data_sources(
|
||||
'--username',
|
||||
shlex.quote(
|
||||
borgmatic.hooks.credential.parse.resolve_credential(
|
||||
database['username']
|
||||
database['username'], config
|
||||
)
|
||||
),
|
||||
)
|
||||
@ -309,8 +312,11 @@ def restore_data_source_dump(
|
||||
connection_params['port'] or data_source.get('restore_port', data_source.get('port', ''))
|
||||
)
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
(
|
||||
connection_params['username']
|
||||
or data_source.get('restore_username', data_source.get('username'))
|
||||
),
|
||||
config,
|
||||
)
|
||||
|
||||
all_databases = bool(data_source['name'] == 'all')
|
||||
@ -364,7 +370,7 @@ def restore_data_source_dump(
|
||||
)
|
||||
|
||||
extra_environment = make_extra_environment(
|
||||
data_source, restore_connection_params=connection_params
|
||||
data_source, config, restore_connection_params=connection_params
|
||||
)
|
||||
|
||||
logger.debug(f"Restoring PostgreSQL database {data_source['name']}{dry_run_label}")
|
||||
|
@ -51,13 +51,13 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
try:
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('username')
|
||||
hook_config.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('password')
|
||||
hook_config.get('password'), config
|
||||
)
|
||||
access_token = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('access_token')
|
||||
hook_config.get('access_token'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Ntfy credential error: {error}')
|
||||
|
@ -42,7 +42,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
|
||||
try:
|
||||
integration_key = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('integration_key')
|
||||
hook_config.get('integration_key'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'PagerDuty credential error: {error}')
|
||||
|
@ -35,8 +35,10 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
state_config = hook_config.get(state.name.lower(), {})
|
||||
|
||||
try:
|
||||
token = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('token'))
|
||||
user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'))
|
||||
token = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('token'), config
|
||||
)
|
||||
user = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('user'), config)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Pushover credential error: {error}')
|
||||
return
|
||||
|
@ -37,9 +37,15 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev
|
||||
)
|
||||
|
||||
try:
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('username'))
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('password'))
|
||||
api_key = borgmatic.hooks.credential.parse.resolve_credential(hook_config.get('api_key'))
|
||||
username = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('username'), config
|
||||
)
|
||||
password = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('password'), config
|
||||
)
|
||||
api_key = borgmatic.hooks.credential.parse.resolve_credential(
|
||||
hook_config.get('api_key'), config
|
||||
)
|
||||
except ValueError as error:
|
||||
logger.warning(f'Zabbix credential error: {error}')
|
||||
return
|
||||
|
@ -19,6 +19,7 @@ encryption_passphrase: yourpassphrase
|
||||
But if you'd rather store them outside of borgmatic, whether for convenience
|
||||
or security reasons, read on.
|
||||
|
||||
|
||||
### Delegating to another application
|
||||
|
||||
borgmatic supports calling another application such as a password manager to
|
||||
@ -31,15 +32,6 @@ to provide the passphrase:
|
||||
encryption_passcommand: pass path/to/borg-passphrase
|
||||
```
|
||||
|
||||
Another example for [KeePassXC](https://keepassxc.org/):
|
||||
|
||||
```yaml
|
||||
encryption_passcommand: keepassxc-cli show --show-protected --attributes Password credentials.kdbx borg_passphrase
|
||||
```
|
||||
|
||||
... where `borg_passphrase` is the title of the KeePassXC entry containing your
|
||||
Borg encryption passphrase in its `Password` field.
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.9</span> Instead of
|
||||
letting Borg run the passcommand—potentially multiple times since borgmatic runs
|
||||
Borg multiple times—borgmatic now runs the passcommand itself and passes the
|
||||
@ -48,9 +40,9 @@ should only ever get prompted for your password manager's passphrase at most
|
||||
once per borgmatic run.
|
||||
|
||||
|
||||
### Using systemd service credentials
|
||||
### systemd service credentials
|
||||
|
||||
borgmatic supports using encrypted [systemd
|
||||
borgmatic supports reading encrypted [systemd
|
||||
credentials](https://systemd.io/CREDENTIALS/). To use this feature, start by
|
||||
saving your password as an encrypted credential to
|
||||
`/etc/credstore.encrypted/borgmatic.pw`, e.g.,
|
||||
@ -146,13 +138,172 @@ The one exception is `borgmatic config validate`, which doesn't actually load
|
||||
any credentials and should continue working anywhere.
|
||||
|
||||
|
||||
### Container secrets
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> When
|
||||
running inside a container, borgmatic can read [Docker
|
||||
secrets](https://docs.docker.com/compose/how-tos/use-secrets/) and [Podman
|
||||
secrets](https://www.redhat.com/en/blog/new-podman-secrets-command). Creating
|
||||
those secrets and passing them into your borgmatic container is outside the
|
||||
scope of this documentation, but here's a simple example of that with [Docker
|
||||
Compose](https://docs.docker.com/compose/):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
borgmatic:
|
||||
# Use the actual image name of your borgmatic container here.
|
||||
image: borgmatic:latest
|
||||
secrets:
|
||||
- borgmatic_passphrase
|
||||
secrets:
|
||||
borgmatic_passphrase:
|
||||
file: /etc/borgmatic/passphrase.txt
|
||||
```
|
||||
|
||||
This assumes there's a file on the host at `/etc/borgmatic/passphrase.txt`
|
||||
containing your passphrase. Docker or Podman mounts the contents of that file
|
||||
into a secret named `borgmatic_passphrase` in the borgmatic container at
|
||||
`/run/secrets/`.
|
||||
|
||||
Once your container secret is in place, you can consume it within your borgmatic
|
||||
configuration file:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential container borgmatic_passphrase}"
|
||||
```
|
||||
|
||||
This reads the secret securely from a file mounted at
|
||||
`/run/secrets/borgmatic_passphrase` within the borgmatic container.
|
||||
|
||||
The `{credential ...}` syntax works for several different options in a borgmatic
|
||||
configuration file besides just `encryption_passphrase`. For instance, the
|
||||
username, password, and API token options within database and monitoring hooks
|
||||
support `{credential ...}`:
|
||||
|
||||
```yaml
|
||||
postgresql_databases:
|
||||
- name: invoices
|
||||
username: postgres
|
||||
password: "{credential container borgmatic_db1}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
You can also optionally override the `/run/secrets` directory that borgmatic reads secrets from
|
||||
inside a container:
|
||||
|
||||
```yaml
|
||||
container:
|
||||
secrets_directory: /path/to/secrets
|
||||
```
|
||||
|
||||
But you should only need to do this for development or testing purposes.
|
||||
|
||||
|
||||
### KeePassXC passwords
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> borgmatic
|
||||
supports reading passwords from the [KeePassXC](https://keepassxc.org/) password
|
||||
manager. To use this feature, start by creating an entry in your KeePassXC
|
||||
database, putting your password into the "Password" field of that entry and
|
||||
making sure it's saved.
|
||||
|
||||
Then, you can consume that password in your borgmatic configuration file. For
|
||||
instance, if the entry's title is "borgmatic" and your KeePassXC database is
|
||||
located at `/etc/keys.kdbx`, do this:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential keepassxc /etc/keys.kdbx borgmatic}"
|
||||
```
|
||||
|
||||
But if the entry's title is multiple words like `borg pw`, you'll
|
||||
need to quote it:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential keepassxc /etc/keys.kdbx 'borg pw'}"
|
||||
```
|
||||
|
||||
With this in place, borgmatic runs the `keepassxc-cli` command to retrieve the
|
||||
passphrase on demand. But note that `keepassxc-cli` will prompt for its own
|
||||
passphrase in order to unlock its database, so be prepared to enter it when
|
||||
running borgmatic.
|
||||
|
||||
The `{credential ...}` syntax works for several different options in a borgmatic
|
||||
configuration file besides just `encryption_passphrase`. For instance, the
|
||||
username, password, and API token options within database and monitoring hooks
|
||||
support `{credential ...}`:
|
||||
|
||||
```yaml
|
||||
postgresql_databases:
|
||||
- name: invoices
|
||||
username: postgres
|
||||
password: "{credential keepassxc /etc/keys.kdbx database}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
You can also optionally override the `keepassxc-cli` command that borgmatic calls to load
|
||||
passwords:
|
||||
|
||||
```yaml
|
||||
keepassxc:
|
||||
keepassxc_cli_command: /usr/local/bin/keepassxc-cli
|
||||
```
|
||||
|
||||
|
||||
### File-based credentials
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.9.11</span> borgmatic
|
||||
supports reading credentials from arbitrary file paths. To use this feature,
|
||||
start by writing your credential into a file that borgmatic has permission to
|
||||
read. Take care not to include anything in the file other than your credential.
|
||||
(borgmatic is smart enough to strip off a trailing newline though.)
|
||||
|
||||
You can consume that credential file in your borgmatic configuration. For
|
||||
instance, if your credential file is at `/credentials/borgmatic.txt`, do this:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: "{credential file /credentials/borgmatic.txt}"
|
||||
```
|
||||
|
||||
With this in place, borgmatic reads the credential from the file path.
|
||||
|
||||
The `{credential ...}` syntax works for several different options in a borgmatic
|
||||
configuration file besides just `encryption_passphrase`. For instance, the
|
||||
username, password, and API token options within database and monitoring hooks
|
||||
support `{credential ...}`:
|
||||
|
||||
```yaml
|
||||
postgresql_databases:
|
||||
- name: invoices
|
||||
username: postgres
|
||||
password: "{credential file /credentials/database.txt}"
|
||||
```
|
||||
|
||||
For specifics about which options are supported, see the
|
||||
[configuration
|
||||
reference](https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
|
||||
|
||||
### Environment variable interpolation
|
||||
|
||||
<span class="minilink minilink-addedin">New in version 1.6.4</span> borgmatic
|
||||
supports interpolating arbitrary environment variables directly into option
|
||||
values in your configuration file. That means you can instruct borgmatic to
|
||||
pull your repository passphrase, your database passwords, or any other option
|
||||
values from environment variables. For instance:
|
||||
values from environment variables.
|
||||
|
||||
Be aware though that environment variables may be less secure than some of the
|
||||
other approaches above for getting credentials into borgmatic. That's because
|
||||
environment variables may be visible from within child processes and/or OS-level
|
||||
process metadata.
|
||||
|
||||
Here's an example of using an environment variable from borgmatic's
|
||||
configuration file:
|
||||
|
||||
```yaml
|
||||
encryption_passphrase: ${YOUR_PASSPHRASE}
|
||||
@ -214,6 +365,7 @@ can escape it with a backslash. For instance, if your password is literally
|
||||
encryption_passphrase: \${A}@!
|
||||
```
|
||||
|
||||
|
||||
## Related features
|
||||
|
||||
Another way to override particular options within a borgmatic configuration
|
||||
@ -226,9 +378,3 @@ Additionally, borgmatic action hooks support their own [variable
|
||||
interpolation](https://torsion.org/borgmatic/docs/how-to/add-preparation-and-cleanup-steps-to-backups/#variable-interpolation),
|
||||
although in that case it's for particular borgmatic runtime values rather than
|
||||
(only) environment variables.
|
||||
|
||||
Lastly, if you do want to specify your passhprase directly within borgmatic
|
||||
configuration, but you'd like to keep it in a separate file from your main
|
||||
configuration, you can [use a configuration include or a merge
|
||||
include](https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#configuration-includes)
|
||||
to pull in an external password.
|
||||
|
BIN
docs/static/docker.png
vendored
Normal file
BIN
docs/static/docker.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
docs/static/keepassxc.png
vendored
Normal file
BIN
docs/static/keepassxc.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
docs/static/podman.png
vendored
Normal file
BIN
docs/static/podman.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
docs/static/systemd.png
vendored
BIN
docs/static/systemd.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 9.7 KiB After Width: | Height: | Size: 9.9 KiB |
29
tests/end-to-end/commands/fake_keepassxc_cli.py
Normal file
29
tests/end-to-end/commands/fake_keepassxc_cli.py
Normal file
@ -0,0 +1,29 @@
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
|
||||
def parse_arguments(*unparsed_arguments):
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('command')
|
||||
parser.add_argument('--show-protected', action='store_true')
|
||||
parser.add_argument('--attributes')
|
||||
parser.add_argument('database_path')
|
||||
parser.add_argument('attribute_name')
|
||||
|
||||
return parser.parse_args(unparsed_arguments)
|
||||
|
||||
|
||||
def main():
|
||||
arguments = parse_arguments(*sys.argv[1:])
|
||||
|
||||
assert arguments.command == 'show'
|
||||
assert arguments.show_protected
|
||||
assert arguments.attributes == 'Password'
|
||||
assert arguments.database_path.endswith('.kdbx')
|
||||
assert arguments.attribute_name
|
||||
|
||||
print('test')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
68
tests/end-to-end/hooks/credential/test_container.py
Normal file
68
tests/end-to-end/hooks/credential/test_container.py
Normal file
@ -0,0 +1,68 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path, secrets_directory):
|
||||
'''
|
||||
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||
to work for testing, including updating the source directories, injecting the given repository
|
||||
path, and tacking on an encryption passphrase loaded from container secrets in the given secrets
|
||||
directory.
|
||||
'''
|
||||
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||
config = (
|
||||
open(config_path)
|
||||
.read()
|
||||
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
|
||||
.replace('- path: /mnt/backup', '')
|
||||
.replace('label: local', '')
|
||||
.replace('- /home/user/path with spaces', '')
|
||||
.replace('- /home', f'- {config_path}')
|
||||
.replace('- /etc', '')
|
||||
.replace('- /var/log/syslog*', '')
|
||||
+ '\nencryption_passphrase: "{credential container mysecret}"'
|
||||
+ f'\ncontainer:\n secrets_directory: {secrets_directory}'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_container_secret():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path, repository_path, secrets_directory=temporary_directory)
|
||||
|
||||
secret_path = os.path.join(temporary_directory, 'mysecret')
|
||||
with open(secret_path, 'w') as secret_file:
|
||||
secret_file.write('test')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
)
|
||||
|
||||
# Run borgmatic to generate a backup archive, and then list it to make sure it exists.
|
||||
subprocess.check_call(
|
||||
f'borgmatic --config {config_path}'.split(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).decode(sys.stdout.encoding)
|
||||
parsed_output = json.loads(output)
|
||||
|
||||
assert len(parsed_output) == 1
|
||||
assert len(parsed_output[0]['archives']) == 1
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
68
tests/end-to-end/hooks/credential/test_file.py
Normal file
68
tests/end-to-end/hooks/credential/test_file.py
Normal file
@ -0,0 +1,68 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path, credential_path):
|
||||
'''
|
||||
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||
to work for testing, including updating the source directories, injecting the given repository
|
||||
path, and tacking on an encryption passphrase loaded from file at the given credential path.
|
||||
'''
|
||||
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||
config = (
|
||||
open(config_path)
|
||||
.read()
|
||||
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
|
||||
.replace('- path: /mnt/backup', '')
|
||||
.replace('label: local', '')
|
||||
.replace('- /home/user/path with spaces', '')
|
||||
.replace('- /home', f'- {config_path}')
|
||||
.replace('- /etc', '')
|
||||
.replace('- /var/log/syslog*', '')
|
||||
+ '\nencryption_passphrase: "{credential file '
|
||||
+ credential_path
|
||||
+ '}"'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_file_credential():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
credential_path = os.path.join(temporary_directory, 'mycredential')
|
||||
generate_configuration(config_path, repository_path, credential_path)
|
||||
|
||||
with open(credential_path, 'w') as credential_file:
|
||||
credential_file.write('test')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
)
|
||||
|
||||
# Run borgmatic to generate a backup archive, and then list it to make sure it exists.
|
||||
subprocess.check_call(
|
||||
f'borgmatic --config {config_path}'.split(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).decode(sys.stdout.encoding)
|
||||
parsed_output = json.loads(output)
|
||||
|
||||
assert len(parsed_output) == 1
|
||||
assert len(parsed_output[0]['archives']) == 1
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
67
tests/end-to-end/hooks/credential/test_keepassxc.py
Normal file
67
tests/end-to-end/hooks/credential/test_keepassxc.py
Normal file
@ -0,0 +1,67 @@
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
def generate_configuration(config_path, repository_path):
|
||||
'''
|
||||
Generate borgmatic configuration into a file at the config path, and update the defaults so as
|
||||
to work for testing, including updating the source directories, injecting the given repository
|
||||
path, and tacking on an encryption passphrase loaded from keepassxc-cli.
|
||||
'''
|
||||
subprocess.check_call(f'borgmatic config generate --destination {config_path}'.split(' '))
|
||||
config = (
|
||||
open(config_path)
|
||||
.read()
|
||||
.replace('ssh://user@backupserver/./sourcehostname.borg', repository_path)
|
||||
.replace('- path: /mnt/backup', '')
|
||||
.replace('label: local', '')
|
||||
.replace('- /home/user/path with spaces', '')
|
||||
.replace('- /home', f'- {config_path}')
|
||||
.replace('- /etc', '')
|
||||
.replace('- /var/log/syslog*', '')
|
||||
+ '\nencryption_passphrase: "{credential keepassxc keys.kdbx mypassword}"'
|
||||
+ '\nkeepassxc:\n keepassxc_cli_command: python3 /app/tests/end-to-end/commands/fake_keepassxc_cli.py'
|
||||
)
|
||||
config_file = open(config_path, 'w')
|
||||
config_file.write(config)
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_keepassxc_password():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
generate_configuration(config_path, repository_path)
|
||||
|
||||
database_path = os.path.join(temporary_directory, 'keys.kdbx')
|
||||
with open(database_path, 'w') as database_file:
|
||||
database_file.write('fake KeePassXC database to pacify file existence check')
|
||||
|
||||
subprocess.check_call(
|
||||
f'borgmatic -v 2 --config {config_path} repo-create --encryption repokey'.split(' '),
|
||||
)
|
||||
|
||||
# Run borgmatic to generate a backup archive, and then list it to make sure it exists.
|
||||
subprocess.check_call(
|
||||
f'borgmatic --config {config_path}'.split(' '),
|
||||
)
|
||||
output = subprocess.check_output(
|
||||
f'borgmatic --config {config_path} list --json'.split(' '),
|
||||
).decode(sys.stdout.encoding)
|
||||
parsed_output = json.loads(output)
|
||||
|
||||
assert len(parsed_output) == 1
|
||||
assert len(parsed_output[0]['archives']) == 1
|
||||
finally:
|
||||
os.chdir(original_working_directory)
|
||||
shutil.rmtree(temporary_directory)
|
@ -30,15 +30,13 @@ def generate_configuration(config_path, repository_path):
|
||||
config_file.close()
|
||||
|
||||
|
||||
def test_borgmatic_command():
|
||||
def test_systemd_credential():
|
||||
# Create a Borg repository.
|
||||
temporary_directory = tempfile.mkdtemp()
|
||||
repository_path = os.path.join(temporary_directory, 'test.borg')
|
||||
extract_path = os.path.join(temporary_directory, 'extract')
|
||||
|
||||
original_working_directory = os.getcwd()
|
||||
os.mkdir(extract_path)
|
||||
os.chdir(extract_path)
|
||||
os.chdir(temporary_directory)
|
||||
|
||||
try:
|
||||
config_path = os.path.join(temporary_directory, 'test.yaml')
|
||||
|
@ -26,7 +26,7 @@ def test_make_environment_with_passphrase_should_set_environment():
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': 'pass'})
|
||||
|
||||
@ -34,16 +34,17 @@ def test_make_environment_with_passphrase_should_set_environment():
|
||||
|
||||
|
||||
def test_make_environment_with_credential_tag_passphrase_should_load_it_and_set_environment():
|
||||
config = {'encryption_passphrase': '{credential systemd pass}'}
|
||||
flexmock(module.borgmatic.borg.passcommand).should_receive(
|
||||
'get_passphrase_from_passcommand'
|
||||
).and_return(None)
|
||||
flexmock(module.os).should_receive('pipe').never()
|
||||
flexmock(module.os.environ).should_receive('get').and_return(None)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).with_args('{credential systemd pass}').and_return('pass')
|
||||
'resolve_credential',
|
||||
).with_args('{credential systemd pass}', config).and_return('pass')
|
||||
|
||||
environment = module.make_environment({'encryption_passphrase': '{credential systemd pass}'})
|
||||
environment = module.make_environment(config)
|
||||
|
||||
assert environment.get('BORG_PASSPHRASE') == 'pass'
|
||||
|
||||
|
74
tests/unit/hooks/credential/test_container.py
Normal file
74
tests/unit/hooks/credential/test_container.py
Normal file
@ -0,0 +1,74 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import container as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_secret_name_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('this is invalid',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_secret_from_file():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/run/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/run/secrets/mysecret').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mysecret',))
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_secrets_directory_looks_there_for_secret_file():
|
||||
config = {'container': {'secrets_directory': '/secrets'}}
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/secrets/mysecret').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['container'], config=config, credential_parameters=('mysecret',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_secrets_directory_prefixes_it_with_working_directory():
|
||||
config = {'container': {'secrets_directory': 'secrets'}, 'working_directory': '/working'}
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/working/secrets/mysecret'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/working/secrets/mysecret').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['container'], config=config, credential_parameters=('mysecret',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/run/secrets/mysecret').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mysecret',))
|
68
tests/unit/hooks/credential/test_file.py
Normal file
68
tests/unit/hooks/credential/test_file.py
Normal file
@ -0,0 +1,68 @@
|
||||
import io
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import file as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('this is invalid',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/credentials/mycredential'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/credentials/mycredential').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('/credentials/mycredential',)
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file_using_working_directory():
|
||||
credential_stream = io.StringIO('password')
|
||||
credential_stream.name = '/working/credentials/mycredential'
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/working/credentials/mycredential').and_return(
|
||||
credential_stream
|
||||
)
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={},
|
||||
config={'working_directory': '/working'},
|
||||
credential_parameters=('credentials/mycredential',),
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins = flexmock(sys.modules['builtins'])
|
||||
builtins.should_receive('open').with_args('/credentials/mycredential').and_raise(
|
||||
FileNotFoundError
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('/credentials/mycredential',)
|
||||
)
|
80
tests/unit/hooks/credential/test_keepassxc.py
Normal file
80
tests/unit/hooks/credential/test_keepassxc.py
Normal file
@ -0,0 +1,80 @@
|
||||
import pytest
|
||||
from flexmock import flexmock
|
||||
|
||||
from borgmatic.hooks.credential import keepassxc as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo',), ('foo', 'bar', 'baz')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_missing_database_raises():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.execute).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('database.kdbx', 'mypassword')
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_present_database_fetches_password_from_keepassxc():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(
|
||||
(
|
||||
'keepassxc-cli',
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
'database.kdbx',
|
||||
'mypassword',
|
||||
)
|
||||
).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('database.kdbx', 'mypassword')
|
||||
)
|
||||
== 'password'
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_with_custom_keepassxc_cli_command_calls_it():
|
||||
config = {'keepassxc': {'keepassxc_cli_command': '/usr/local/bin/keepassxc-cli --some-option'}}
|
||||
flexmock(module.os.path).should_receive('exists').and_return(True)
|
||||
flexmock(module.borgmatic.execute).should_receive(
|
||||
'execute_command_and_capture_output'
|
||||
).with_args(
|
||||
(
|
||||
'/usr/local/bin/keepassxc-cli',
|
||||
'--some-option',
|
||||
'show',
|
||||
'--show-protected',
|
||||
'--attributes',
|
||||
'Password',
|
||||
'database.kdbx',
|
||||
'mypassword',
|
||||
)
|
||||
).and_return(
|
||||
'password'
|
||||
).once()
|
||||
|
||||
assert (
|
||||
module.load_credential(
|
||||
hook_config=config['keepassxc'],
|
||||
config=config,
|
||||
credential_parameters=('database.kdbx', 'mypassword'),
|
||||
)
|
||||
== 'password'
|
||||
)
|
@ -4,39 +4,82 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.credential import parse as module
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_string_without_credential_tag():
|
||||
def test_hash_adapter_is_always_equal():
|
||||
assert module.Hash_adapter({1: 2}) == module.Hash_adapter({3: 4})
|
||||
|
||||
|
||||
def test_hash_adapter_alwaysh_hashes_the_same():
|
||||
assert hash(module.Hash_adapter({1: 2})) == hash(module.Hash_adapter({3: 4}))
|
||||
|
||||
|
||||
def test_cache_ignoring_unhashable_arguments_caches_arguments_after_first_call():
|
||||
hashable = 3
|
||||
unhashable = {1, 2}
|
||||
calls = 0
|
||||
|
||||
@module.cache_ignoring_unhashable_arguments
|
||||
def function(first, second, third):
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
|
||||
assert first == hashable
|
||||
assert second == unhashable
|
||||
assert third == unhashable
|
||||
|
||||
return first
|
||||
|
||||
assert function(hashable, unhashable, third=unhashable) == hashable
|
||||
assert calls == 1
|
||||
|
||||
assert function(hashable, unhashable, third=unhashable) == hashable
|
||||
assert calls == 1
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_string_without_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
assert module.resolve_credential('{no credentials here}') == '{no credentials here}'
|
||||
assert module.resolve_credential('{no credentials here}', config={}) == '{no credentials here}'
|
||||
|
||||
|
||||
def test_resolve_credential_passes_through_none():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
assert module.resolve_credential(None) is None
|
||||
assert module.resolve_credential(None, config={}) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize('invalid_value', ('{credential}', '{credential }', '{credential systemd}'))
|
||||
def test_resolve_credential_with_invalid_credential_tag_raises(invalid_value):
|
||||
def test_resolve_credential_with_invalid_credential_raises(invalid_value):
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.resolve_credential(invalid_value)
|
||||
module.resolve_credential(invalid_value, config={})
|
||||
|
||||
|
||||
def test_resolve_credential_with_valid_credential_tag_loads_credential():
|
||||
def test_resolve_credential_with_valid_credential_loads_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
('mycredential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
|
||||
|
||||
def test_resolve_credential_with_valid_credential_and_quoted_parameters_loads_credential():
|
||||
module.resolve_credential.cache_clear()
|
||||
flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hook').with_args(
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
('my credential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd "my credential"}', config={}) == 'result'
|
||||
|
||||
|
||||
def test_resolve_credential_caches_credential_after_first_call():
|
||||
@ -45,8 +88,8 @@ def test_resolve_credential_caches_credential_after_first_call():
|
||||
'load_credential',
|
||||
{},
|
||||
'systemd',
|
||||
'mycredential',
|
||||
('mycredential',),
|
||||
).and_return('result').once()
|
||||
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}') == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
assert module.resolve_credential('{credential systemd mycredential}', config={}) == 'result'
|
||||
|
@ -7,13 +7,23 @@ from flexmock import flexmock
|
||||
from borgmatic.hooks.credential import systemd as module
|
||||
|
||||
|
||||
@pytest.mark.parametrize('credential_parameters', ((), ('foo', 'bar')))
|
||||
def test_load_credential_with_invalid_credential_parameters_raises(credential_parameters):
|
||||
flexmock(module.os.environ).should_receive('get').never()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=credential_parameters
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_without_credentials_directory_raises():
|
||||
flexmock(module.os.environ).should_receive('get').with_args('CREDENTIALS_DIRECTORY').and_return(
|
||||
None
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
|
||||
|
||||
def test_load_credential_with_invalid_credential_name_raises():
|
||||
@ -22,7 +32,9 @@ def test_load_credential_with_invalid_credential_name_raises():
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='../../my!@#$credential')
|
||||
module.load_credential(
|
||||
hook_config={}, config={}, credential_parameters=('../../my!@#$credential',)
|
||||
)
|
||||
|
||||
|
||||
def test_load_credential_reads_named_credential_from_file():
|
||||
@ -35,7 +47,7 @@ def test_load_credential_reads_named_credential_from_file():
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_return(credential_stream)
|
||||
|
||||
assert (
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
== 'password'
|
||||
)
|
||||
|
||||
@ -48,4 +60,4 @@ def test_load_credential_with_file_not_found_error_raises():
|
||||
builtins.should_receive('open').with_args('/var/mycredential').and_raise(FileNotFoundError)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
module.load_credential(hook_config={}, config={}, credential_name='mycredential')
|
||||
module.load_credential(hook_config={}, config={}, credential_parameters=('mycredential',))
|
||||
|
@ -9,7 +9,7 @@ from borgmatic.hooks.data_source import mariadb as module
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
extra_environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, extra_environment, dry_run=False)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
@ -18,7 +18,7 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=True)
|
||||
|
||||
assert names == ()
|
||||
|
||||
@ -27,13 +27,13 @@ def test_database_names_to_dump_queries_mariadb_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mariadb', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=False)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@ -55,7 +55,7 @@ def test_dump_data_sources_dumps_each_database():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
@ -63,6 +63,7 @@ def test_dump_data_sources_dumps_each_database():
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
@ -89,13 +90,14 @@ def test_dump_data_sources_dumps_with_password():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
@ -119,10 +121,11 @@ def test_dump_data_sources_dumps_all_databases_at_once():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
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={},
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
extra_environment=object,
|
||||
@ -146,12 +149,13 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
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={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
@ -186,7 +190,7 @@ def test_database_names_to_dump_runs_mariadb_with_list_options():
|
||||
extra_environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mariadb_with_list_options():
|
||||
@ -207,7 +211,7 @@ 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, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mariadb_dump():
|
||||
@ -216,7 +220,7 @@ def test_execute_dump_command_runs_mariadb_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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -235,6 +239,7 @@ def test_execute_dump_command_runs_mariadb_dump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -251,7 +256,7 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -269,6 +274,7 @@ def test_execute_dump_command_runs_mariadb_dump_without_add_drop_database():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -285,7 +291,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -310,6 +316,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_hostname_and_port():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -326,7 +333,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -347,6 +354,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_username_and_password():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
@ -363,7 +371,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -383,6 +391,7 @@ def test_execute_dump_command_runs_mariadb_dump_with_options():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -399,7 +408,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -423,6 +432,7 @@ def test_execute_dump_command_runs_non_default_mariadb_dump_with_options():
|
||||
'mariadb_dump_command': 'custom_mariadb_dump',
|
||||
'options': '--stuff=such',
|
||||
}, # Custom MariaDB dump command specified
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -442,6 +452,7 @@ def test_execute_dump_command_with_duplicate_dump_skips_mariadb_dump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -457,7 +468,7 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@ -465,6 +476,7 @@ def test_execute_dump_command_with_dry_run_skips_mariadb_dump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -480,7 +492,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@ -502,7 +514,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@ -527,7 +539,7 @@ def test_restore_data_source_dump_runs_mariadb_to_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch'),
|
||||
processes=[extract_process],
|
||||
@ -558,7 +570,7 @@ def test_restore_data_source_dump_runs_mariadb_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@ -591,7 +603,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mariadb', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@ -622,7 +634,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@ -662,7 +674,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mariadb', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
@ -703,7 +715,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@ -757,7 +769,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mariadb',
|
||||
@ -798,7 +810,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
@ -126,7 +126,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -247,9 +247,9 @@ 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(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
command = module.build_dump_command(database, dump_filename='test', dump_format='archive')
|
||||
command = module.build_dump_command(database, {}, dump_filename='test', dump_format='archive')
|
||||
|
||||
assert "'bob; naughty-command'" in command
|
||||
|
||||
@ -262,7 +262,7 @@ def test_restore_data_source_dump_runs_mongorestore():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop'],
|
||||
processes=[extract_process],
|
||||
@ -296,7 +296,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_hostname_and_port():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@ -344,7 +344,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_username_and_password()
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@ -398,7 +398,7 @@ def test_restore_data_source_dump_with_connection_params_uses_connection_params_
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@ -456,7 +456,7 @@ def test_restore_data_source_dump_without_connection_params_uses_restore_params_
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@ -502,7 +502,7 @@ def test_restore_data_source_dump_runs_mongorestore_with_options():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive', '--drop', '--harder'],
|
||||
processes=[extract_process],
|
||||
@ -534,7 +534,7 @@ def test_restore_databases_dump_runs_mongorestore_with_schemas():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
[
|
||||
'mongorestore',
|
||||
@ -574,7 +574,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--archive'],
|
||||
processes=[extract_process],
|
||||
@ -605,7 +605,7 @@ def test_restore_data_source_dump_with_dry_run_skips_restore():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
@ -631,7 +631,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
['mongorestore', '--dir', '/dump/path', '--drop'],
|
||||
processes=[],
|
||||
|
@ -9,7 +9,7 @@ from borgmatic.hooks.data_source import mysql as module
|
||||
def test_database_names_to_dump_passes_through_name():
|
||||
extra_environment = flexmock()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'foo'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'foo'}, {}, extra_environment, dry_run=False)
|
||||
|
||||
assert names == ('foo',)
|
||||
|
||||
@ -18,10 +18,10 @@ def test_database_names_to_dump_bails_for_dry_run():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=True)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=True)
|
||||
|
||||
assert names == ()
|
||||
|
||||
@ -30,13 +30,13 @@ def test_database_names_to_dump_queries_mysql_for_database_names():
|
||||
extra_environment = flexmock()
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('mysql', '--skip-column-names', '--batch', '--execute', 'show schemas'),
|
||||
extra_environment=extra_environment,
|
||||
).and_return('foo\nbar\nmysql\n').once()
|
||||
|
||||
names = module.database_names_to_dump({'name': 'all'}, extra_environment, dry_run=False)
|
||||
names = module.database_names_to_dump({'name': 'all'}, {}, extra_environment, dry_run=False)
|
||||
|
||||
assert names == ('foo', 'bar')
|
||||
|
||||
@ -63,6 +63,7 @@ def test_dump_data_sources_dumps_each_database():
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
@ -89,13 +90,14 @@ def test_dump_data_sources_dumps_with_password():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('database_names_to_dump').and_return(('foo',)).and_return(
|
||||
('bar',)
|
||||
)
|
||||
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database=database,
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
@ -120,6 +122,7 @@ def test_dump_data_sources_dumps_all_databases_at_once():
|
||||
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={},
|
||||
dump_path=object,
|
||||
database_names=('foo', 'bar'),
|
||||
extra_environment=object,
|
||||
@ -146,6 +149,7 @@ def test_dump_data_sources_dumps_all_databases_separately_when_format_configured
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
flexmock(module).should_receive('execute_dump_command').with_args(
|
||||
database={'name': name, 'format': 'sql'},
|
||||
config={},
|
||||
dump_path=object,
|
||||
database_names=(name,),
|
||||
extra_environment=object,
|
||||
@ -180,7 +184,7 @@ def test_database_names_to_dump_runs_mysql_with_list_options():
|
||||
extra_environment=None,
|
||||
).and_return(('foo\nbar')).once()
|
||||
|
||||
assert module.database_names_to_dump(database, None, '') == ('foo', 'bar')
|
||||
assert module.database_names_to_dump(database, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_database_names_to_dump_runs_non_default_mysql_with_list_options():
|
||||
@ -201,7 +205,7 @@ 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, {}, None, '') == ('foo', 'bar')
|
||||
|
||||
|
||||
def test_execute_dump_command_runs_mysqldump():
|
||||
@ -210,7 +214,7 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -229,6 +233,7 @@ def test_execute_dump_command_runs_mysqldump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -245,7 +250,7 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -263,6 +268,7 @@ def test_execute_dump_command_runs_mysqldump_without_add_drop_database():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'add_drop_database': False},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -279,7 +285,7 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -304,6 +310,7 @@ def test_execute_dump_command_runs_mysqldump_with_hostname_and_port():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'hostname': 'database.example.org', 'port': 5433},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -320,7 +327,7 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -341,6 +348,7 @@ def test_execute_dump_command_runs_mysqldump_with_username_and_password():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'username': 'root', 'password': 'trustsome1'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment={'MYSQL_PWD': 'trustsome1'},
|
||||
@ -357,7 +365,7 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -377,6 +385,7 @@ def test_execute_dump_command_runs_mysqldump_with_options():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo', 'options': '--stuff=such'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -393,7 +402,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -415,6 +424,7 @@ def test_execute_dump_command_runs_non_default_mysqldump():
|
||||
'name': 'foo',
|
||||
'mysql_dump_command': 'custom_mysqldump',
|
||||
}, # Custom MySQL dump command specified
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -434,6 +444,7 @@ def test_execute_dump_command_with_duplicate_dump_skips_mysqldump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -449,7 +460,7 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
@ -457,6 +468,7 @@ def test_execute_dump_command_with_dry_run_skips_mysqldump():
|
||||
assert (
|
||||
module.execute_dump_command(
|
||||
database={'name': 'foo'},
|
||||
config={},
|
||||
dump_path=flexmock(),
|
||||
database_names=('foo',),
|
||||
extra_environment=None,
|
||||
@ -472,7 +484,7 @@ def test_dump_data_sources_errors_for_missing_all_databases():
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@ -494,7 +506,7 @@ def test_dump_data_sources_does_not_error_for_missing_all_databases_with_dry_run
|
||||
flexmock(module).should_receive('make_dump_path').and_return('')
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return(
|
||||
'databases/localhost/all'
|
||||
)
|
||||
@ -519,7 +531,7 @@ def test_restore_data_source_dump_runs_mysql_to_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch'),
|
||||
processes=[extract_process],
|
||||
@ -550,7 +562,7 @@ def test_restore_data_source_dump_runs_mysql_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@ -581,7 +593,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('custom_mysql', '--batch', '--harder'),
|
||||
processes=[extract_process],
|
||||
@ -612,7 +624,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@ -652,7 +664,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
('mysql', '--batch', '--user', 'root'),
|
||||
processes=[extract_process],
|
||||
@ -693,7 +705,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@ -747,7 +759,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').with_args(
|
||||
(
|
||||
'mysql',
|
||||
@ -788,7 +800,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_with_processes').never()
|
||||
|
||||
module.restore_data_source_dump(
|
||||
|
@ -26,9 +26,9 @@ def test_make_extra_environment_maps_options_to_environment():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
extra_env = module.make_extra_environment(database)
|
||||
extra_env = module.make_extra_environment(database, {})
|
||||
|
||||
assert extra_env == expected
|
||||
|
||||
@ -37,10 +37,10 @@ def test_make_extra_environment_with_cli_password_sets_correct_password():
|
||||
database = {'name': 'foo', 'restore_password': 'trustsome1', 'password': 'anotherpassword'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
|
||||
extra = module.make_extra_environment(
|
||||
database, restore_connection_params={'password': 'clipassword'}
|
||||
database, {}, restore_connection_params={'password': 'clipassword'}
|
||||
)
|
||||
|
||||
assert extra['PGPASSWORD'] == 'clipassword'
|
||||
@ -50,7 +50,7 @@ def test_make_extra_environment_without_cli_password_or_configured_password_does
|
||||
database = {'name': 'foo'}
|
||||
|
||||
extra = module.make_extra_environment(
|
||||
database, restore_connection_params={'username': 'someone'}
|
||||
database, {}, restore_connection_params={'username': 'someone'}
|
||||
)
|
||||
|
||||
assert 'PGPASSWORD' not in extra
|
||||
@ -59,7 +59,7 @@ def test_make_extra_environment_without_cli_password_or_configured_password_does
|
||||
def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
|
||||
database = {'name': 'foo'}
|
||||
|
||||
extra = module.make_extra_environment(database)
|
||||
extra = module.make_extra_environment(database, {})
|
||||
|
||||
assert 'PGSSLMODE' not in extra
|
||||
|
||||
@ -67,41 +67,41 @@ def test_make_extra_environment_without_ssl_mode_does_not_set_ssl_mode():
|
||||
def test_database_names_to_dump_passes_through_individual_database_name():
|
||||
database = {'name': 'foo'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_individual_database_name_with_format():
|
||||
database = {'name': 'foo', 'format': 'custom'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_passes_through_all_without_format():
|
||||
database = {'name': 'all'}
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('all',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('all',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_and_dry_run_bails():
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').never()
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=True) == ()
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=True) == ()
|
||||
|
||||
|
||||
def test_database_names_to_dump_with_all_and_format_lists_databases():
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\nbar,test,"stuff and such"'
|
||||
)
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@ -111,7 +111,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
|
||||
database = {'name': 'all', 'format': 'custom', 'hostname': 'localhost', 'port': 1234}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@ -128,7 +128,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_hostnam
|
||||
extra_environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@ -138,7 +138,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
|
||||
database = {'name': 'all', 'format': 'custom', 'username': 'postgres'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'psql',
|
||||
@ -153,7 +153,7 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_usernam
|
||||
extra_environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@ -163,13 +163,13 @@ def test_database_names_to_dump_with_all_and_format_lists_databases_with_options
|
||||
database = {'name': 'all', 'format': 'custom', 'list_options': '--harder'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
('psql', '--list', '--no-password', '--no-psqlrc', '--csv', '--tuples-only', '--harder'),
|
||||
extra_environment=object,
|
||||
).and_return('foo,test,\nbar,test,"stuff and such"')
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == (
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == (
|
||||
'foo',
|
||||
'bar',
|
||||
)
|
||||
@ -179,12 +179,12 @@ def test_database_names_to_dump_with_all_and_format_excludes_particular_database
|
||||
database = {'name': 'all', 'format': 'custom'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').and_return(
|
||||
'foo,test,\ntemplate0,test,blah'
|
||||
)
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
@ -195,7 +195,7 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
|
||||
(
|
||||
'docker',
|
||||
@ -213,7 +213,7 @@ def test_database_names_to_dump_with_all_and_psql_command_uses_custom_command():
|
||||
extra_environment=object,
|
||||
).and_return('foo,text').once()
|
||||
|
||||
assert module.database_names_to_dump(database, flexmock(), dry_run=False) == ('foo',)
|
||||
assert module.database_names_to_dump(database, {}, flexmock(), dry_run=False) == ('foo',)
|
||||
|
||||
|
||||
def test_use_streaming_true_for_any_non_directory_format_databases():
|
||||
@ -248,7 +248,7 @@ def test_dump_data_sources_runs_pg_dump_for_each_database():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
for name, process in zip(('foo', 'bar'), processes):
|
||||
@ -355,7 +355,7 @@ def test_dump_data_sources_with_dry_run_skips_pg_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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
flexmock(module).should_receive('execute_command').never()
|
||||
|
||||
@ -384,7 +384,7 @@ def test_dump_data_sources_runs_pg_dump_with_hostname_and_port():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -432,7 +432,7 @@ def test_dump_data_sources_runs_pg_dump_with_username_and_password():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -478,7 +478,7 @@ def test_dump_data_sources_with_username_injection_attack_gets_escaped():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -521,7 +521,7 @@ def test_dump_data_sources_runs_pg_dump_with_directory_format():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_parent_directory_for_dump')
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump').never()
|
||||
|
||||
@ -566,7 +566,7 @@ def test_dump_data_sources_runs_pg_dump_with_options():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -609,7 +609,7 @@ def test_dump_data_sources_runs_pg_dumpall_for_all_databases():
|
||||
flexmock(module.os.path).should_receive('exists').and_return(False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -641,7 +641,7 @@ def test_dump_data_sources_runs_non_default_pg_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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.dump).should_receive('create_named_pipe_for_dump')
|
||||
|
||||
flexmock(module).should_receive('execute_command').with_args(
|
||||
@ -679,7 +679,7 @@ def test_restore_data_source_dump_runs_pg_restore():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -736,7 +736,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_hostname_and_port():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -801,7 +801,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_username_and_password():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'trustsome1', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@ -875,7 +875,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'clipassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@ -957,7 +957,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return(
|
||||
{'PGPASSWORD': 'restorepassword', 'PGSSLMODE': 'disable'}
|
||||
)
|
||||
@ -1033,7 +1033,7 @@ def test_restore_data_source_dump_runs_pg_restore_with_options():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -1090,7 +1090,7 @@ def test_restore_data_source_dump_runs_psql_for_all_database_dump():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -1132,7 +1132,7 @@ def test_restore_data_source_dump_runs_psql_for_plain_database_dump():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -1186,7 +1186,7 @@ def test_restore_data_source_dump_runs_non_default_pg_restore_and_psql():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -1250,7 +1250,7 @@ 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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename')
|
||||
@ -1277,7 +1277,7 @@ def test_restore_data_source_dump_without_extract_process_restores_from_disk():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
@ -1332,7 +1332,7 @@ def test_restore_data_source_dump_with_schemas_restores_schemas():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module).should_receive('make_extra_environment').and_return({'PGSSLMODE': 'disable'})
|
||||
flexmock(module).should_receive('make_dump_path')
|
||||
flexmock(module.dump).should_receive('make_data_source_dump_filename').and_return('/dump/path')
|
||||
|
@ -38,7 +38,7 @@ def test_ping_monitor_minimal_config_hits_hosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -62,7 +62,7 @@ def test_ping_monitor_with_access_token_hits_hosted_ntfy_on_fail():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -88,7 +88,7 @@ def test_ping_monitor_with_username_password_and_access_token_ignores_username_p
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -114,7 +114,7 @@ def test_ping_monitor_with_username_password_hits_hosted_ntfy_on_fail():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -135,7 +135,7 @@ def test_ping_monitor_with_password_but_no_username_warns():
|
||||
hook_config = {'topic': topic, 'password': 'fakepassword'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -157,7 +157,7 @@ def test_ping_monitor_with_username_but_no_password_warns():
|
||||
hook_config = {'topic': topic, 'username': 'testuser'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -179,7 +179,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_start():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -196,7 +196,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_finish():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -213,7 +213,7 @@ def test_ping_monitor_minimal_config_hits_selfhosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic, 'server': custom_base_url}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{custom_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -234,7 +234,7 @@ def test_ping_monitor_minimal_config_does_not_hit_hosted_ntfy_on_fail_dry_run():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -251,7 +251,7 @@ def test_ping_monitor_custom_message_hits_hosted_ntfy_on_fail():
|
||||
hook_config = {'topic': topic, 'fail': custom_message_config}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}', headers=custom_message_headers, auth=None
|
||||
).and_return(flexmock(ok=True)).once()
|
||||
@ -270,7 +270,7 @@ def test_ping_monitor_custom_state_hits_hosted_ntfy_on_start():
|
||||
hook_config = {'topic': topic, 'states': ['start', 'fail']}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.START),
|
||||
@ -291,7 +291,7 @@ def test_ping_monitor_with_connection_error_logs_warning():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{default_base_url}/{topic}',
|
||||
headers=return_default_message_headers(borgmatic.hooks.monitoring.monitor.State.FAIL),
|
||||
@ -331,7 +331,7 @@ def test_ping_monitor_with_other_error_logs_warning():
|
||||
hook_config = {'topic': topic}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
response = flexmock(ok=False)
|
||||
response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.exceptions.RequestException
|
||||
|
@ -6,7 +6,7 @@ from borgmatic.hooks.monitoring import pagerduty as module
|
||||
def test_ping_monitor_ignores_start_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -22,7 +22,7 @@ def test_ping_monitor_ignores_start_state():
|
||||
def test_ping_monitor_ignores_finish_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -38,7 +38,7 @@ def test_ping_monitor_ignores_finish_state():
|
||||
def test_ping_monitor_calls_api_for_fail_state():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True))
|
||||
|
||||
module.ping_monitor(
|
||||
@ -54,7 +54,7 @@ def test_ping_monitor_calls_api_for_fail_state():
|
||||
def test_ping_monitor_dry_run_does_not_call_api():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
module.ping_monitor(
|
||||
@ -70,7 +70,7 @@ def test_ping_monitor_dry_run_does_not_call_api():
|
||||
def test_ping_monitor_with_connection_error_logs_warning():
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').and_raise(
|
||||
module.requests.exceptions.ConnectionError
|
||||
)
|
||||
@ -107,7 +107,7 @@ def test_ping_monitor_with_other_error_logs_warning():
|
||||
response = flexmock(ok=False)
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.exceptions.RequestException
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -43,7 +43,7 @@ def test_ping_monitor_config_with_minimum_config_start_state_backup_not_send_to_
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -71,7 +71,7 @@ def test_ping_monitor_start_state_backup_default_message_successfully_send_to_pu
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -107,7 +107,7 @@ def test_ping_monitor_start_state_backup_custom_message_successfully_send_to_pus
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -142,7 +142,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -180,7 +180,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -218,7 +218,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_emergency
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -259,7 +259,7 @@ def test_ping_monitor_start_state_backup_default_message_with_priority_high_decl
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -314,7 +314,7 @@ def test_ping_monitor_start_state_backup_based_on_documentation_advanced_example
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -380,7 +380,7 @@ def test_ping_monitor_fail_state_backup_based_on_documentation_advanced_example_
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -451,7 +451,7 @@ def test_ping_monitor_finish_state_backup_based_on_documentation_advanced_exampl
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
'https://api.pushover.net/1/messages.json',
|
||||
@ -487,7 +487,7 @@ def test_ping_monitor_config_with_minimum_config_fail_state_backup_successfully_
|
||||
hook_config = {'token': 'ksdjfwoweijfvwoeifvjmwghagy92', 'user': '983hfe0of902lkjfa2amanfgui'}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
|
||||
|
||||
@ -511,7 +511,7 @@ def test_ping_monitor_config_incorrect_state_exit_early():
|
||||
}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').never()
|
||||
flexmock(module.requests).should_receive('post').and_return(flexmock(ok=True)).never()
|
||||
|
||||
@ -537,7 +537,7 @@ def test_ping_monitor_push_post_error_bails():
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
push_response = flexmock(ok=False)
|
||||
push_response.should_receive('raise_for_status').and_raise(
|
||||
module.requests.ConnectionError
|
||||
|
@ -77,7 +77,7 @@ def test_ping_monitor_config_with_api_key_only_bails():
|
||||
hook_config = {'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -97,7 +97,7 @@ def test_ping_monitor_config_with_host_only_bails():
|
||||
hook_config = {'host': HOST}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -117,7 +117,7 @@ def test_ping_monitor_config_with_key_only_bails():
|
||||
hook_config = {'key': KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -137,7 +137,7 @@ def test_ping_monitor_config_with_server_only_bails():
|
||||
hook_config = {'server': SERVER}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -156,7 +156,7 @@ def test_ping_monitor_config_user_password_no_zabbix_data_bails():
|
||||
hook_config = {'server': SERVER, 'username': USERNAME, 'password': PASSWORD}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -175,7 +175,7 @@ def test_ping_monitor_config_api_key_no_zabbix_data_bails():
|
||||
hook_config = {'server': SERVER, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -195,7 +195,7 @@ def test_ping_monitor_config_itemid_no_auth_data_bails():
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -215,7 +215,7 @@ def test_ping_monitor_config_host_and_key_no_auth_data_bails():
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -235,7 +235,7 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
|
||||
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: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
@ -257,7 +257,7 @@ def test_ping_monitor_config_host_and_missing_key_bails():
|
||||
hook_config = {'server': SERVER, 'host': HOST, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -275,7 +275,7 @@ def test_ping_monitor_config_key_and_missing_host_bails():
|
||||
hook_config = {'server': SERVER, 'key': KEY, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -302,7 +302,7 @@ 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: value)
|
||||
).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}
|
||||
@ -343,7 +343,7 @@ 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: value)
|
||||
).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}
|
||||
@ -384,7 +384,7 @@ def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bai
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -408,7 +408,7 @@ def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bai
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.logger).should_receive('warning').once()
|
||||
flexmock(module.requests).should_receive('post').never()
|
||||
|
||||
@ -428,7 +428,7 @@ def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
|
||||
hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).replace_with(lambda value, config: value)
|
||||
flexmock(module.requests).should_receive('post').with_args(
|
||||
f'{SERVER}',
|
||||
headers=AUTH_HEADERS_API_KEY,
|
||||
@ -453,7 +453,7 @@ 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: value)
|
||||
).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}
|
||||
@ -488,7 +488,7 @@ def test_ping_monitor_config_itemid_with_username_password_auth_data_and_push_po
|
||||
|
||||
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
|
||||
'resolve_credential'
|
||||
).replace_with(lambda value: value)
|
||||
).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}
|
||||
|
Loading…
x
Reference in New Issue
Block a user