Deprecated configuration options warning logging.

This commit is contained in:
Dan Helfman 2023-06-29 10:03:36 -07:00
parent e2c95327fb
commit 9cf27fa4ba
7 changed files with 140 additions and 51 deletions

5
NEWS
View file

@ -7,6 +7,11 @@
* #720: Fix an error when dumping a MySQL database and the "exclude_nodump" option is set. * #720: Fix an error when dumping a MySQL database and the "exclude_nodump" option is set.
* When merging two configuration files, error gracefully if the two files do not adhere to the same * When merging two configuration files, error gracefully if the two files do not adhere to the same
format. format.
* BREAKING: Remove the deprecated (and silently ignored) "--successful" flag on the "list" action,
as newer versions of Borg list successful (non-checkpoint) archives by default.
* All deprecated configuration option values now generate warning logs.
* Remove the deprecated (and non-functional) "--excludes" flag in favor of excludes within
configuration.
1.7.15 1.7.15
* #326: Add configuration options and command-line flags for backing up a database from one * #326: Add configuration options and command-line flags for backing up a database from one

View file

@ -14,7 +14,6 @@ ARCHIVE_FILTER_FLAGS_MOVED_TO_RLIST = ('prefix', 'match_archives', 'sort_by', 'f
MAKE_FLAGS_EXCLUDES = ( MAKE_FLAGS_EXCLUDES = (
'repository', 'repository',
'archive', 'archive',
'successful',
'paths', 'paths',
'find_paths', 'find_paths',
) + ARCHIVE_FILTER_FLAGS_MOVED_TO_RLIST ) + ARCHIVE_FILTER_FLAGS_MOVED_TO_RLIST

View file

@ -274,11 +274,6 @@ def make_parsers():
default=config_paths, default=config_paths,
help=f"Configuration filenames or directories, defaults to: {' '.join(unexpanded_config_paths)}", help=f"Configuration filenames or directories, defaults to: {' '.join(unexpanded_config_paths)}",
) )
global_group.add_argument(
'--excludes',
dest='excludes_filename',
help='Deprecated in favor of exclude_patterns within configuration',
)
global_group.add_argument( global_group.add_argument(
'-n', '-n',
'--dry-run', '--dry-run',
@ -1098,12 +1093,6 @@ def make_parsers():
metavar='PATTERN', metavar='PATTERN',
help='Only list archive names matching this pattern', help='Only list archive names matching this pattern',
) )
list_group.add_argument(
'--successful',
default=True,
action='store_true',
help='Deprecated; no effect. Newer versions of Borg shows successful (non-checkpoint) archives by default.',
)
list_group.add_argument( list_group.add_argument(
'--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys' '--sort-by', metavar='KEYS', help='Comma-separated list of sorting keys'
) )
@ -1279,11 +1268,6 @@ def parse_arguments(*unparsed_arguments):
f"Unrecognized argument{'s' if len(unknown_arguments) > 1 else ''}: {' '.join(unknown_arguments)}" f"Unrecognized argument{'s' if len(unknown_arguments) > 1 else ''}: {' '.join(unknown_arguments)}"
) )
if arguments['global'].excludes_filename:
raise ValueError(
'The --excludes flag has been replaced with exclude_patterns in configuration.'
)
if 'create' in arguments and arguments['create'].list_files and arguments['create'].progress: if 'create' in arguments and arguments['create'].list_files and arguments['create'].progress:
raise ValueError( raise ValueError(
'With the create action, only one of --list (--files) and --progress flags can be used.' 'With the create action, only one of --list (--files) and --progress flags can be used.'

View file

@ -12,52 +12,143 @@ def normalize(config_filename, config):
location = config.get('location') or {} location = config.get('location') or {}
storage = config.get('storage') or {} storage = config.get('storage') or {}
consistency = config.get('consistency') or {} consistency = config.get('consistency') or {}
retention = config.get('retention') or {}
hooks = config.get('hooks') or {} hooks = config.get('hooks') or {}
# Upgrade exclude_if_present from a string to a list. # Upgrade exclude_if_present from a string to a list.
exclude_if_present = location.get('exclude_if_present') exclude_if_present = location.get('exclude_if_present')
if isinstance(exclude_if_present, str): if isinstance(exclude_if_present, str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The exclude_if_present option now expects a list value. String values for this option are deprecated and support will be removed from a future release.',
)
)
)
config['location']['exclude_if_present'] = [exclude_if_present] config['location']['exclude_if_present'] = [exclude_if_present]
# Upgrade various monitoring hooks from a string to a dict. # Upgrade various monitoring hooks from a string to a dict.
healthchecks = hooks.get('healthchecks') healthchecks = hooks.get('healthchecks')
if isinstance(healthchecks, str): if isinstance(healthchecks, str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The healthchecks hook now expects a mapping value. String values for this option are deprecated and support will be removed from a future release.',
)
)
)
config['hooks']['healthchecks'] = {'ping_url': healthchecks} config['hooks']['healthchecks'] = {'ping_url': healthchecks}
cronitor = hooks.get('cronitor') cronitor = hooks.get('cronitor')
if isinstance(cronitor, str): if isinstance(cronitor, str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The healthchecks hook now expects key/value pairs. String values for this option are deprecated and support will be removed from a future release.',
)
)
)
config['hooks']['cronitor'] = {'ping_url': cronitor} config['hooks']['cronitor'] = {'ping_url': cronitor}
pagerduty = hooks.get('pagerduty') pagerduty = hooks.get('pagerduty')
if isinstance(pagerduty, str): if isinstance(pagerduty, str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The healthchecks hook now expects key/value pairs. String values for this option are deprecated and support will be removed from a future release.',
)
)
)
config['hooks']['pagerduty'] = {'integration_key': pagerduty} config['hooks']['pagerduty'] = {'integration_key': pagerduty}
cronhub = hooks.get('cronhub') cronhub = hooks.get('cronhub')
if isinstance(cronhub, str): if isinstance(cronhub, str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The healthchecks hook now expects key/value pairs. String values for this option are deprecated and support will be removed from a future release.',
)
)
)
config['hooks']['cronhub'] = {'ping_url': cronhub} config['hooks']['cronhub'] = {'ping_url': cronhub}
# Upgrade consistency checks from a list of strings to a list of dicts. # Upgrade consistency checks from a list of strings to a list of dicts.
checks = consistency.get('checks') checks = consistency.get('checks')
if isinstance(checks, list) and len(checks) and isinstance(checks[0], str): if isinstance(checks, list) and len(checks) and isinstance(checks[0], str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The checks option now expects a list of key/value pairs. Lists of strings for this option are deprecated and support will be removed from a future release.',
)
)
)
config['consistency']['checks'] = [{'name': check_type} for check_type in checks] config['consistency']['checks'] = [{'name': check_type} for check_type in checks]
# Rename various configuration options. # Rename various configuration options.
numeric_owner = location.pop('numeric_owner', None) numeric_owner = location.pop('numeric_owner', None)
if numeric_owner is not None: if numeric_owner is not None:
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The numeric_owner option has been renamed to numeric_ids. numeric_owner is deprecated and support will be removed from a future release.',
)
)
)
config['location']['numeric_ids'] = numeric_owner config['location']['numeric_ids'] = numeric_owner
bsd_flags = location.pop('bsd_flags', None) bsd_flags = location.pop('bsd_flags', None)
if bsd_flags is not None: if bsd_flags is not None:
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The bsd_flags option has been renamed to flags. bsd_flags is deprecated and support will be removed from a future release.',
)
)
)
config['location']['flags'] = bsd_flags config['location']['flags'] = bsd_flags
remote_rate_limit = storage.pop('remote_rate_limit', None) remote_rate_limit = storage.pop('remote_rate_limit', None)
if remote_rate_limit is not None: if remote_rate_limit is not None:
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The remote_rate_limit option has been renamed to upload_rate_limit. remote_rate_limit is deprecated and support will be removed from a future release.',
)
)
)
config['storage']['upload_rate_limit'] = remote_rate_limit config['storage']['upload_rate_limit'] = remote_rate_limit
# Upgrade remote repositories to ssh:// syntax, required in Borg 2. # Upgrade remote repositories to ssh:// syntax, required in Borg 2.
repositories = location.get('repositories') repositories = location.get('repositories')
if repositories: if repositories:
if isinstance(repositories[0], str): if isinstance(repositories[0], str):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The repositories option now expects a list of key/value pairs. Lists of strings for this option are deprecated and support will be removed from a future release.',
)
)
)
config['location']['repositories'] = [ config['location']['repositories'] = [
{'path': repository} for repository in repositories {'path': repository} for repository in repositories
] ]
@ -71,7 +162,7 @@ def normalize(config_filename, config):
dict( dict(
levelno=logging.WARNING, levelno=logging.WARNING,
levelname='WARNING', levelname='WARNING',
msg=f'{config_filename}: Repository paths containing "~" are deprecated in borgmatic and no longer work in Borg 2.x+.', msg=f'{config_filename}: Repository paths containing "~" are deprecated in borgmatic and support will be removed from a future release.',
) )
) )
) )
@ -95,7 +186,7 @@ def normalize(config_filename, config):
dict( dict(
levelno=logging.WARNING, levelno=logging.WARNING,
levelname='WARNING', levelname='WARNING',
msg=f'{config_filename}: Remote repository paths without ssh:// syntax are deprecated. Interpreting "{repository_path}" as "{rewritten_repository_path}"', msg=f'{config_filename}: Remote repository paths without ssh:// syntax are deprecated and support will be removed from a future release. Interpreting "{repository_path}" as "{rewritten_repository_path}"',
) )
) )
) )
@ -108,4 +199,15 @@ def normalize(config_filename, config):
else: else:
config['location']['repositories'].append(repository_dict) config['location']['repositories'].append(repository_dict)
if consistency.get('prefix') or retention.get('prefix'):
logs.append(
logging.makeLogRecord(
dict(
levelno=logging.WARNING,
levelname='WARNING',
msg=f'{config_filename}: The prefix option is deprecated and support will be removed from a future release. Use archive_name_format or match_archives instead.',
)
)
)
return logs return logs

View file

@ -12,7 +12,6 @@ def test_parse_arguments_with_no_arguments_uses_defaults():
global_arguments = arguments['global'] global_arguments = arguments['global']
assert global_arguments.config_paths == config_paths assert global_arguments.config_paths == config_paths
assert global_arguments.excludes_filename is None
assert global_arguments.verbosity == 0 assert global_arguments.verbosity == 0
assert global_arguments.syslog_verbosity == 0 assert global_arguments.syslog_verbosity == 0
assert global_arguments.log_file_verbosity == 0 assert global_arguments.log_file_verbosity == 0
@ -71,7 +70,6 @@ def test_parse_arguments_with_verbosity_overrides_default():
global_arguments = arguments['global'] global_arguments = arguments['global']
assert global_arguments.config_paths == config_paths assert global_arguments.config_paths == config_paths
assert global_arguments.excludes_filename is None
assert global_arguments.verbosity == 1 assert global_arguments.verbosity == 1
assert global_arguments.syslog_verbosity == 0 assert global_arguments.syslog_verbosity == 0
assert global_arguments.log_file_verbosity == 0 assert global_arguments.log_file_verbosity == 0
@ -85,7 +83,6 @@ def test_parse_arguments_with_syslog_verbosity_overrides_default():
global_arguments = arguments['global'] global_arguments = arguments['global']
assert global_arguments.config_paths == config_paths assert global_arguments.config_paths == config_paths
assert global_arguments.excludes_filename is None
assert global_arguments.verbosity == 0 assert global_arguments.verbosity == 0
assert global_arguments.syslog_verbosity == 2 assert global_arguments.syslog_verbosity == 2
@ -98,7 +95,6 @@ def test_parse_arguments_with_log_file_verbosity_overrides_default():
global_arguments = arguments['global'] global_arguments = arguments['global']
assert global_arguments.config_paths == config_paths assert global_arguments.config_paths == config_paths
assert global_arguments.excludes_filename is None
assert global_arguments.verbosity == 0 assert global_arguments.verbosity == 0
assert global_arguments.syslog_verbosity == 0 assert global_arguments.syslog_verbosity == 0
assert global_arguments.log_file_verbosity == -1 assert global_arguments.log_file_verbosity == -1
@ -234,13 +230,6 @@ def test_parse_arguments_disallows_invalid_argument():
module.parse_arguments('--posix-me-harder') module.parse_arguments('--posix-me-harder')
def test_parse_arguments_disallows_deprecated_excludes_option():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
with pytest.raises(ValueError):
module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
def test_parse_arguments_disallows_encryption_mode_without_init(): def test_parse_arguments_disallows_encryption_mode_without_init():
flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default']) flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])

View file

@ -46,7 +46,7 @@ def test_parse_configuration_transforms_file_into_mapping():
- /etc - /etc
repositories: repositories:
- hostname.borg - path: hostname.borg
retention: retention:
keep_minutely: 60 keep_minutely: 60
@ -83,7 +83,7 @@ def test_parse_configuration_passes_through_quoted_punctuation():
- "/home/{escaped_punctuation}" - "/home/{escaped_punctuation}"
repositories: repositories:
- test.borg - path: test.borg
''' '''
) )
@ -106,7 +106,7 @@ def test_parse_configuration_with_schema_lacking_examples_does_not_raise():
- /home - /home
repositories: repositories:
- hostname.borg - path: hostname.borg
''', ''',
''' '''
map: map:
@ -135,7 +135,7 @@ def test_parse_configuration_inlines_include():
- /home - /home
repositories: repositories:
- hostname.borg - path: hostname.borg
retention: retention:
!include include.yaml !include include.yaml
@ -168,7 +168,7 @@ def test_parse_configuration_merges_include():
- /home - /home
repositories: repositories:
- hostname.borg - path: hostname.borg
retention: retention:
keep_daily: 1 keep_daily: 1
@ -221,7 +221,7 @@ def test_parse_configuration_raises_for_validation_error():
location: location:
source_directories: yes source_directories: yes
repositories: repositories:
- hostname.borg - path: hostname.borg
''' '''
) )
@ -237,7 +237,7 @@ def test_parse_configuration_applies_overrides():
- /home - /home
repositories: repositories:
- hostname.borg - path: hostname.borg
local_path: borg1 local_path: borg1
''' '''
@ -265,7 +265,7 @@ def test_parse_configuration_applies_normalization():
- /home - /home
repositories: repositories:
- hostname.borg - path: hostname.borg
exclude_if_present: .nobackup exclude_if_present: .nobackup
''' '''
@ -280,4 +280,4 @@ def test_parse_configuration_applies_normalization():
'exclude_if_present': ['.nobackup'], 'exclude_if_present': ['.nobackup'],
} }
} }
assert logs == [] assert logs

View file

@ -9,7 +9,7 @@ from borgmatic.config import normalize as module
( (
{'location': {'exclude_if_present': '.nobackup'}}, {'location': {'exclude_if_present': '.nobackup'}},
{'location': {'exclude_if_present': ['.nobackup']}}, {'location': {'exclude_if_present': ['.nobackup']}},
False, True,
), ),
( (
{'location': {'exclude_if_present': ['.nobackup']}}, {'location': {'exclude_if_present': ['.nobackup']}},
@ -39,22 +39,22 @@ from borgmatic.config import normalize as module
( (
{'hooks': {'healthchecks': 'https://example.com'}}, {'hooks': {'healthchecks': 'https://example.com'}},
{'hooks': {'healthchecks': {'ping_url': 'https://example.com'}}}, {'hooks': {'healthchecks': {'ping_url': 'https://example.com'}}},
False, True,
), ),
( (
{'hooks': {'cronitor': 'https://example.com'}}, {'hooks': {'cronitor': 'https://example.com'}},
{'hooks': {'cronitor': {'ping_url': 'https://example.com'}}}, {'hooks': {'cronitor': {'ping_url': 'https://example.com'}}},
False, True,
), ),
( (
{'hooks': {'pagerduty': 'https://example.com'}}, {'hooks': {'pagerduty': 'https://example.com'}},
{'hooks': {'pagerduty': {'integration_key': 'https://example.com'}}}, {'hooks': {'pagerduty': {'integration_key': 'https://example.com'}}},
False, True,
), ),
( (
{'hooks': {'cronhub': 'https://example.com'}}, {'hooks': {'cronhub': 'https://example.com'}},
{'hooks': {'cronhub': {'ping_url': 'https://example.com'}}}, {'hooks': {'cronhub': {'ping_url': 'https://example.com'}}},
False, True,
), ),
( (
{'hooks': None}, {'hooks': None},
@ -64,12 +64,12 @@ from borgmatic.config import normalize as module
( (
{'consistency': {'checks': ['archives']}}, {'consistency': {'checks': ['archives']}},
{'consistency': {'checks': [{'name': 'archives'}]}}, {'consistency': {'checks': [{'name': 'archives'}]}},
False, True,
), ),
( (
{'consistency': {'checks': ['archives']}}, {'consistency': {'checks': ['archives']}},
{'consistency': {'checks': [{'name': 'archives'}]}}, {'consistency': {'checks': [{'name': 'archives'}]}},
False, True,
), ),
( (
{'consistency': None}, {'consistency': None},
@ -79,17 +79,17 @@ from borgmatic.config import normalize as module
( (
{'location': {'numeric_owner': False}}, {'location': {'numeric_owner': False}},
{'location': {'numeric_ids': False}}, {'location': {'numeric_ids': False}},
False, True,
), ),
( (
{'location': {'bsd_flags': False}}, {'location': {'bsd_flags': False}},
{'location': {'flags': False}}, {'location': {'flags': False}},
False, True,
), ),
( (
{'storage': {'remote_rate_limit': False}}, {'storage': {'remote_rate_limit': False}},
{'storage': {'upload_rate_limit': False}}, {'storage': {'upload_rate_limit': False}},
False, True,
), ),
( (
{'location': {'repositories': ['foo@bar:/repo']}}, {'location': {'repositories': ['foo@bar:/repo']}},
@ -109,12 +109,12 @@ from borgmatic.config import normalize as module
( (
{'location': {'repositories': ['ssh://foo@bar:1234/repo']}}, {'location': {'repositories': ['ssh://foo@bar:1234/repo']}},
{'location': {'repositories': [{'path': 'ssh://foo@bar:1234/repo'}]}}, {'location': {'repositories': [{'path': 'ssh://foo@bar:1234/repo'}]}},
False, True,
), ),
( (
{'location': {'repositories': ['file:///repo']}}, {'location': {'repositories': ['file:///repo']}},
{'location': {'repositories': [{'path': '/repo'}]}}, {'location': {'repositories': [{'path': '/repo'}]}},
False, True,
), ),
( (
{'location': {'repositories': [{'path': 'foo@bar:/repo', 'label': 'foo'}]}}, {'location': {'repositories': [{'path': 'foo@bar:/repo', 'label': 'foo'}]}},
@ -131,6 +131,16 @@ from borgmatic.config import normalize as module
{'location': {'repositories': [{'path': '/repo', 'label': 'foo'}]}}, {'location': {'repositories': [{'path': '/repo', 'label': 'foo'}]}},
False, False,
), ),
(
{'consistency': {'prefix': 'foo'}},
{'consistency': {'prefix': 'foo'}},
True,
),
(
{'retention': {'prefix': 'foo'}},
{'retention': {'prefix': 'foo'}},
True,
),
), ),
) )
def test_normalize_applies_hard_coded_normalization_to_config( def test_normalize_applies_hard_coded_normalization_to_config(