Browse Source

Require "prefix" in retention section when "archive_name_format" is set.

Dan 1 year ago
parent
commit
43d0e597a2

+ 2
- 0
.gitignore View File

@@ -1,6 +1,8 @@
1 1
 *.egg-info
2 2
 *.pyc
3 3
 *.swp
4
+.cache
5
+.coverage
4 6
 .tox
5 7
 build
6 8
 dist

+ 4
- 2
NEWS View File

@@ -2,8 +2,10 @@
2 2
  * #16, #38: Support for user-defined hooks before/after backup, or on error.
3 3
  * #33: Improve clarity of logging spew at high verbosity levels.
4 4
  * #29: Support for using tilde in source directory path to reference home directory.
5
- * Converted main source repository from Mercurial to Git.
6
- * Updated dead links to Borg documentation.
5
+ * Require "prefix" in retention section when "archive_name_format" is set. This is to avoid
6
+   accidental pruning of archives with a different archive name format.
7
+ * Convert main source repository from Mercurial to Git.
8
+ * Update dead links to Borg documentation.
7 9
 
8 10
 1.1.8
9 11
  * #39: Fix to make /etc/borgmatic/config.yaml optional rather than required when using the default

+ 3
- 1
borgmatic/config/schema.yaml View File

@@ -94,7 +94,9 @@ map:
94 94
                 desc: |
95 95
                     Name of the archive. Borg placeholders can be used. See the output of
96 96
                     "borg help placeholders" for details. Default is
97
-                    "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}"
97
+                    "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must
98
+                    also specify a prefix in the retention section to avoid accidental pruning of
99
+                    archives with a different archive name format.
98 100
                 example: "{hostname}-documents-{now}"
99 101
     retention:
100 102
         desc: |

+ 27
- 15
borgmatic/config/validate.py View File

@@ -25,6 +25,31 @@ class Validation_error(ValueError):
25 25
         self.config_filename = config_filename
26 26
         self.error_messages = error_messages
27 27
 
28
+    def __str__(self):
29
+        '''
30
+        Render a validation error as a user-facing string.
31
+        '''
32
+        return 'An error occurred while parsing a configuration file at {}:\n'.format(
33
+            self.config_filename
34
+        ) + '\n'.join(self.error_messages)
35
+
36
+
37
+def apply_logical_validation(config_filename, parsed_configuration):
38
+    '''
39
+    Given a parsed and schematically valid configuration as a data structure of nested dicts (see
40
+    below), run through any additional logical validation checks. If there are any such validation
41
+    problems, raise a Validation_error.
42
+    '''
43
+    archive_name_format = parsed_configuration.get('storage', {}).get('archive_name_format')
44
+    prefix = parsed_configuration.get('retention', {}).get('prefix')
45
+
46
+    if archive_name_format and not prefix:
47
+        raise Validation_error(
48
+            config_filename, (
49
+                'If you provide an archive_name_format, you must also specify a retention prefix.',
50
+            )
51
+        )
52
+
28 53
 
29 54
 def parse_configuration(config_filename, schema_filename):
30 55
     '''
@@ -58,19 +83,6 @@ def parse_configuration(config_filename, schema_filename):
58 83
     if validator.validation_errors:
59 84
         raise Validation_error(config_filename, validator.validation_errors)
60 85
 
61
-    return parsed_result
62
-
86
+    apply_logical_validation(config_filename, parsed_result)
63 87
 
64
-def display_validation_error(validation_error):
65
-    '''
66
-    Given a Validation_error, display its error messages to stderr.
67
-    '''
68
-    print(
69
-        'An error occurred while parsing a configuration file at {}:'.format(
70
-            validation_error.config_filename
71
-        ),
72
-        file=sys.stderr,
73
-    )
74
-
75
-    for error in validation_error.error_messages:
76
-        print(error, file=sys.stderr)
88
+    return parsed_result

+ 0
- 7
borgmatic/tests/integration/config/test_validate.py View File

@@ -148,10 +148,3 @@ def test_parse_configuration_raises_for_validation_error():
148 148
 
149 149
     with pytest.raises(module.Validation_error):
150 150
         module.parse_configuration('config.yaml', 'schema.yaml')
151
-
152
-
153
-def test_display_validation_error_does_not_raise():
154
-    flexmock(sys.modules['builtins']).should_receive('print')
155
-    error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
156
-
157
-    module.display_validation_error(error)

+ 43
- 0
borgmatic/tests/unit/config/test_validate.py View File

@@ -0,0 +1,43 @@
1
+import pytest
2
+
3
+from borgmatic.config import validate as module
4
+
5
+
6
+def test_validation_error_str_contains_error_messages_and_config_filename():
7
+    error = module.Validation_error('config.yaml', ('oops', 'uh oh'))
8
+
9
+    result = str(error)
10
+
11
+    assert 'config.yaml' in result
12
+    assert 'oops' in result
13
+    assert 'uh oh' in result
14
+
15
+
16
+def test_apply_logical_validation_raises_if_archive_name_format_present_without_prefix():
17
+    with pytest.raises(module.Validation_error):
18
+        module.apply_logical_validation(
19
+            'config.yaml',
20
+            {
21
+                'storage': {'archive_name_format': '{hostname}-{now}'},
22
+                'retention': {'keep_daily': 7},
23
+            },
24
+        )
25
+
26
+
27
+def test_apply_logical_validation_does_not_raise_if_archive_name_format_and_prefix_present():
28
+    module.apply_logical_validation(
29
+        'config.yaml',
30
+        {
31
+            'storage': {'archive_name_format': '{hostname}-{now}'},
32
+            'retention': {'prefix': '{hostname}-'},
33
+        },
34
+    )
35
+
36
+
37
+def test_apply_logical_validation_does_not_raise_otherwise():
38
+    module.apply_logical_validation(
39
+        'config.yaml',
40
+        {
41
+            'retention': {'keep_secondly': 1000},
42
+        },
43
+    )

Loading…
Cancel
Save