For the MariaDB and MySQL database hooks, add a "skip_names" option to ignore particular databases when dumping "all" (#973).

This commit is contained in:
Dan Helfman
2025-10-16 14:48:42 -07:00
parent d808d7424f
commit 4017897b1a
6 changed files with 150 additions and 0 deletions
+2
View File
@@ -1,6 +1,8 @@
2.0.10.dev0
* #942: Factor reference material out of the documentation how-to guides. This means there's now a
whole reference section in the docs! Check it out: https://torsion.org/borgmatic/
* #973: For the MariaDB and MySQL database hooks, add a "skip_names" option to ignore particular
databases when dumping "all".
* #1150: Fix for a runtime directory error when the "create" action is used with the "--log-json"
flag.
* #1161: Fix a traceback (TypeError) in the "check" action with Python 3.14.
+18
View File
@@ -1546,6 +1546,15 @@ properties:
database hook implicitly enables read_special (see
above) to support dump and restore streaming.
example: users
skip_names:
type: array
items:
type: string
description: |
Database names to skip when dumping "all" databases.
Ignored when the database name is not "all".
example:
- cache
label:
type: string
description: |
@@ -1728,6 +1737,15 @@ properties:
database hook implicitly enables read_special (see
above) to support dump and restore streaming.
example: users
skip_names:
type: array
items:
type: string
description: |
Database names to skip when dumping "all" databases.
Ignored when the database name is not "all".
example:
- cache
label:
type: string
description: |
+11
View File
@@ -112,7 +112,14 @@ def database_names_to_dump(database, config, username, password, environment, dr
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.
'''
skip_names = database.get('skip_names')
if database['name'] != 'all':
if skip_names:
logger.warning(
f'For MariaDB database {database["name"]}, ignoring the "skip_names" option, which is only supported for database "all"'
)
return (database['name'],)
if dry_run:
@@ -144,12 +151,16 @@ def database_names_to_dump(database, config, username, password, environment, dr
logger.debug('Querying for "all" MariaDB databases to dump')
if skip_names:
logger.debug(f'Skipping database names: {", ".join(skip_names)}')
show_output = execute_command_and_capture_output(show_command, environment=environment)
return tuple(
show_name
for show_name in show_output.strip().splitlines()
if show_name not in SYSTEM_DATABASE_NAMES
if not skip_names or show_name not in skip_names
)
+11
View File
@@ -35,7 +35,14 @@ def database_names_to_dump(database, config, username, password, environment, dr
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.
'''
skip_names = database.get('skip_names')
if database['name'] != 'all':
if skip_names:
logger.warning(
f'For MySQL database {database["name"]}, ignoring the "skip_names" option, which is only supported for database "all"'
)
return (database['name'],)
if dry_run:
@@ -73,12 +80,16 @@ def database_names_to_dump(database, config, username, password, environment, dr
logger.debug('Querying for "all" MySQL databases to dump')
if skip_names:
logger.debug(f'Skipping database names: {", ".join(skip_names)}')
show_output = execute_command_and_capture_output(show_command, environment=environment)
return tuple(
show_name
for show_name in show_output.strip().splitlines()
if show_name not in SYSTEM_DATABASE_NAMES
if not skip_names or show_name not in skip_names
)
@@ -142,6 +142,23 @@ def test_database_names_to_dump_passes_through_name():
assert names == ('foo',)
def test_database_names_to_dump_with_non_all_name_and_skip_names_warns():
environment = flexmock()
flexmock(module.logger).should_receive('warning').once()
names = module.database_names_to_dump(
{'name': 'foo', 'skip_names': ('foo', 'bar')},
{},
'root',
'trustsome1',
environment,
dry_run=False,
)
assert names == ('foo',)
def test_database_names_to_dump_bails_for_dry_run():
environment = flexmock()
flexmock(module).should_receive('execute_command_and_capture_output').never()
@@ -193,6 +210,41 @@ def test_database_names_to_dump_queries_mariadb_for_database_names():
assert names == ('foo', 'bar')
def test_database_names_to_dump_with_database_name_all_and_skip_names_filters_out_unwanted_databases():
environment = flexmock()
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential',
).replace_with(lambda value, config: value)
flexmock(module).should_receive('parse_extra_options').and_return((), None)
flexmock(module).should_receive('make_defaults_file_options').with_args(
'root',
'trustsome1',
None,
).and_return(('--defaults-extra-file=/dev/fd/99',))
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
(
'mariadb',
'--defaults-extra-file=/dev/fd/99',
'--skip-column-names',
'--batch',
'--execute',
'show schemas',
),
environment=environment,
).and_return('foo\nbar\nbaz\nmysql\n').once()
names = module.database_names_to_dump(
{'name': 'all', 'skip_names': ('foo', 'bar')},
{},
'root',
'trustsome1',
environment,
dry_run=False,
)
assert names == ('baz',)
def test_database_names_to_dump_with_environment_password_transport_skips_defaults_file_and_passes_user_flag():
environment = flexmock()
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
@@ -21,6 +21,23 @@ def test_database_names_to_dump_passes_through_name():
assert names == ('foo',)
def test_database_names_to_dump_with_non_all_name_and_skip_names_warns():
environment = flexmock()
flexmock(module.logger).should_receive('warning').once()
names = module.database_names_to_dump(
{'name': 'foo', 'skip_names': ('foo', 'bar')},
{},
'root',
'trustsome1',
environment,
dry_run=False,
)
assert names == ('foo',)
def test_database_names_to_dump_bails_for_dry_run():
environment = flexmock()
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
@@ -75,6 +92,45 @@ def test_database_names_to_dump_queries_mysql_for_database_names():
assert names == ('foo', 'bar')
def test_database_names_to_dump_with_database_name_all_and_skip_names_filters_out_unwanted_databases():
environment = flexmock()
flexmock(module.borgmatic.hooks.credential.parse).should_receive(
'resolve_credential',
).replace_with(lambda value, config: value)
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
'parse_extra_options'
).and_return((), None)
flexmock(module.borgmatic.hooks.data_source.mariadb).should_receive(
'make_defaults_file_options'
).with_args(
'root',
'trustsome1',
None,
).and_return(('--defaults-extra-file=/dev/fd/99',))
flexmock(module).should_receive('execute_command_and_capture_output').with_args(
(
'mysql',
'--defaults-extra-file=/dev/fd/99',
'--skip-column-names',
'--batch',
'--execute',
'show schemas',
),
environment=environment,
).and_return('foo\nbar\nbaz\nmysql\n').once()
names = module.database_names_to_dump(
{'name': 'all', 'skip_names': ('foo', 'bar')},
{},
'root',
'trustsome1',
environment,
dry_run=False,
)
assert names == ('baz',)
def test_database_names_to_dump_with_environment_password_transport_skips_defaults_file_and_passes_user_flag():
environment = flexmock()
flexmock(module.borgmatic.hooks.credential.parse).should_receive(