Explorar o código

Deprecated configuration options warning logging.

Dan Helfman hai 1 ano
pai
achega
9cf27fa4ba

+ 5 - 0
NEWS

@@ -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

+ 0 - 1
borgmatic/borg/list.py

@@ -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

+ 0 - 16
borgmatic/commands/arguments.py

@@ -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.'

+ 104 - 2
borgmatic/config/normalize.py

@@ -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

+ 0 - 11
tests/integration/commands/test_arguments.py

@@ -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'])
 
 

+ 9 - 9
tests/integration/config/test_validate.py

@@ -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

+ 22 - 12
tests/unit/config/test_normalize.py

@@ -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(