Browse Source

Merge.

master
Dan Helfman 1 year ago
parent
commit
a5aa9355f5

+ 1
- 0
AUTHORS View File

@@ -5,3 +5,4 @@ Henning Schroeder: Copy editing
5 5
 Michele Lazzeri: Custom archive names
6 6
 Robin `ypid` Schneider: Support additional options of Borg
7 7
 Scott Squires: Custom archive names
8
+Johannes Feichtner: Support for user hooks

+ 6
- 0
README.md View File

@@ -66,6 +66,12 @@ To install borgmatic, run the following command to download and install it:
66 66
 Note that your pip binary may have a different name than "pip3". Make sure
67 67
 you're using Python 3, as borgmatic does not support Python 2.
68 68
 
69
+### Docker
70
+
71
+If you would like to run borgmatic within Docker, please take a look at
72
+[b3vis/borgmatic](https://hub.docker.com/r/b3vis/borgmatic/) for more
73
+information.
74
+
69 75
 ## Configuration
70 76
 
71 77
 After you install borgmatic, generate a sample configuration file:

+ 28
- 19
borgmatic/commands/borgmatic.py View File

@@ -1,3 +1,4 @@
1
+
1 2
 from argparse import ArgumentParser
2 3
 import logging
3 4
 import os
@@ -5,6 +6,7 @@ from subprocess import CalledProcessError
5 6
 import sys
6 7
 
7 8
 from borgmatic.borg import check, create, prune
9
+from borgmatic.commands import hook
8 10
 from borgmatic.config import collect, convert, validate
9 11
 from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS, verbosity_to_log_level
10 12
 
@@ -92,29 +94,36 @@ def main():  # pragma: no cover
92 94
         for config_filename in config_filenames:
93 95
             logger.info('{}: Parsing configuration file'.format(config_filename))
94 96
             config = validate.parse_configuration(config_filename, validate.schema_filename())
95
-            (location, storage, retention, consistency) = (
97
+            (location, storage, retention, consistency, hooks) = (
96 98
                 config.get(section_name, {})
97
-                for section_name in ('location', 'storage', 'retention', 'consistency')
99
+                for section_name in ('location', 'storage', 'retention', 'consistency', 'hooks')
98 100
             )
99 101
             remote_path = location.get('remote_path')
100 102
 
101
-            create.initialize(storage)
102
-
103
-            for repository in location['repositories']:
104
-                if args.prune:
105
-                    logger.info('{}: Pruning archives'.format(repository))
106
-                    prune.prune_archives(args.verbosity, repository, retention, remote_path=remote_path)
107
-                if args.create:
108
-                    logger.info('{}: Creating archive'.format(repository))
109
-                    create.create_archive(
110
-                        args.verbosity,
111
-                        repository,
112
-                        location,
113
-                        storage,
114
-                    )
115
-                if args.check:
116
-                    logger.info('{}: Running consistency checks'.format(repository))
117
-                    check.check_archives(args.verbosity, repository, consistency, remote_path=remote_path)
103
+            try:
104
+                create.initialize(storage)
105
+                hook.execute_hook(hooks.get('before_backup'))
106
+
107
+		        for repository in location['repositories']:
108
+		            if args.prune:
109
+		                logger.info('{}: Pruning archives'.format(repository))
110
+		                prune.prune_archives(args.verbosity, repository, retention, remote_path=remote_path)
111
+		            if args.create:
112
+		                logger.info('{}: Creating archive'.format(repository))
113
+		                create.create_archive(
114
+		                    args.verbosity,
115
+		                    repository,
116
+		                    location,
117
+		                    storage,
118
+		                )
119
+		            if args.check:
120
+		                logger.info('{}: Running consistency checks'.format(repository))
121
+		                check.check_archives(args.verbosity, repository, consistency, remote_path=remote_path)
122
+
123
+                hook.execute_hook(hooks.get('after_backup'))
124
+            except (OSError, CalledProcessError):
125
+                hook.execute_hook(hooks.get('on_error'))
126
+                raise
118 127
     except (ValueError, OSError, CalledProcessError) as error:
119 128
         print(error, file=sys.stderr)
120 129
         sys.exit(1)

+ 7
- 0
borgmatic/commands/hook.py View File

@@ -0,0 +1,7 @@
1
+import subprocess
2
+
3
+
4
+def execute_hook(commands):
5
+    if commands:
6
+        for cmd in commands:
7
+            subprocess.check_call(cmd, shell=True)

+ 1
- 1
borgmatic/config/generate.py View File

@@ -24,7 +24,7 @@ def _schema_to_sample_configuration(schema, level=0):
24 24
     for each section based on the schema "desc" description.
25 25
     '''
26 26
     example = schema.get('example')
27
-    if example:
27
+    if example is not None:
28 28
         return example
29 29
 
30 30
     config = yaml.comments.CommentedMap([

+ 25
- 0
borgmatic/config/schema.yaml View File

@@ -157,3 +157,28 @@ map:
157 157
                 desc: Restrict the number of checked archives to the last n. Applies only to the
158 158
                       "archives" check.
159 159
                 example: 3
160
+    hooks:
161
+        desc: |
162
+            Shell commands or scripts to execute before and after a backup or if an error has occurred.
163
+            IMPORTANT: All provided commands and scripts are executed with user permissions of borgmatic.
164
+            Do not forget to set secure permissions on this file as well as on any script listed (chmod 0700) to
165
+            prevent potential shell injection or privilege escalation.
166
+        map:
167
+            before_backup:
168
+                seq:
169
+                    - type: scalar
170
+                desc: List of one or more shell commands or scripts to execute before creating a backup.
171
+                example:
172
+                    - echo "`date` - Starting a backup job."
173
+            after_backup:
174
+                seq:
175
+                    - type: scalar
176
+                desc: List of one or more shell commands or scripts to execute after creating a backup.
177
+                example:
178
+                    - echo "`date` - Backup created."
179
+            on_error:
180
+                seq:
181
+                    - type: scalar
182
+                desc: List of one or more shell commands or scripts to execute in case an exception has occurred.
183
+                example:
184
+                    - echo "`date` - Error while creating a backup."

+ 1
- 1
borgmatic/config/validate.py View File

@@ -50,7 +50,7 @@ def parse_configuration(config_filename, schema_filename):
50 50
     # simply remove all examples before passing the schema to pykwalify.
51 51
     for section_name, section_schema in schema['map'].items():
52 52
         for field_name, field_schema in section_schema['map'].items():
53
-            field_schema.pop('example')
53
+            field_schema.pop('example', None)
54 54
 
55 55
     validator = pykwalify.core.Core(source_data=config, schema_data=schema)
56 56
     parsed_result = validator.validate(raise_exception=False)

+ 10
- 0
borgmatic/tests/unit/borg/test_hook.py View File

@@ -0,0 +1,10 @@
1
+from flexmock import flexmock
2
+
3
+from borgmatic.commands import hook as module
4
+
5
+
6
+def test_execute_hook_invokes_each_command():
7
+    subprocess = flexmock(module.subprocess)
8
+    subprocess.should_receive('check_call').with_args(':', shell=True).once()
9
+
10
+    module.execute_hook([':'])

Loading…
Cancel
Save