From 86511deac4ca0afb6c357dbbf950c8f09fadd09e Mon Sep 17 00:00:00 2001 From: b3vis Date: Thu, 26 Oct 2017 05:24:24 +0100 Subject: [PATCH 1/2] Added section about docker (#18) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 1e10d44..6e370f2 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,11 @@ To install borgmatic, run the following command to download and install it: Note that your pip binary may have a different name than "pip3". Make sure you're using Python 3, as borgmatic does not support Python 2. +### Docker + +If you would like to run borgmatic within docker please take a look at +[b3vis/borgmatic](https://hub.docker.com/r/b3vis/borgmatic/) for more information. + ## Configuration After you install borgmatic, generate a sample configuration file: From f1a98d82c6e40754396cec4770effe74523773f0 Mon Sep 17 00:00:00 2001 From: Johannes Feichtner Date: Thu, 26 Oct 2017 06:38:27 +0200 Subject: [PATCH 2/2] #16, #38: Support for user-defined hooks before/after backup, or on error. --- AUTHORS | 1 + borgmatic/commands/borgmatic.py | 38 ++++++++++++++++---------- borgmatic/commands/hook.py | 7 +++++ borgmatic/config/generate.py | 2 +- borgmatic/config/schema.yaml | 25 +++++++++++++++++ borgmatic/config/validate.py | 2 +- borgmatic/tests/unit/borg/test_hook.py | 10 +++++++ 7 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 borgmatic/commands/hook.py create mode 100644 borgmatic/tests/unit/borg/test_hook.py diff --git a/AUTHORS b/AUTHORS index bd99af8..81ae45e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,3 +5,4 @@ Henning Schroeder: Copy editing Michele Lazzeri: Custom archive names Robin `ypid` Schneider: Support additional options of Borg Scott Squires: Custom archive names +Johannes Feichtner: Support for user hooks diff --git a/borgmatic/commands/borgmatic.py b/borgmatic/commands/borgmatic.py index 2fdff34..e67fe88 100644 --- a/borgmatic/commands/borgmatic.py +++ b/borgmatic/commands/borgmatic.py @@ -5,6 +5,7 @@ from subprocess import CalledProcessError import sys from borgmatic.borg import check, create, prune +from borgmatic.commands import hook from borgmatic.config import collect, convert, validate @@ -84,26 +85,33 @@ def main(): # pragma: no cover for config_filename in config_filenames: config = validate.parse_configuration(config_filename, validate.schema_filename()) - (location, storage, retention, consistency) = ( + (location, storage, retention, consistency, hooks) = ( config.get(section_name, {}) - for section_name in ('location', 'storage', 'retention', 'consistency') + for section_name in ('location', 'storage', 'retention', 'consistency', 'hooks') ) remote_path = location.get('remote_path') - create.initialize(storage) + try: + create.initialize(storage) + hook.execute_hook(hooks.get('before_backup')) - for repository in location['repositories']: - if args.prune: - prune.prune_archives(args.verbosity, repository, retention, remote_path=remote_path) - if args.create: - create.create_archive( - args.verbosity, - repository, - location, - storage, - ) - if args.check: - check.check_archives(args.verbosity, repository, consistency, remote_path=remote_path) + for repository in location['repositories']: + if args.prune: + prune.prune_archives(args.verbosity, repository, retention, remote_path=remote_path) + if args.create: + create.create_archive( + args.verbosity, + repository, + location, + storage, + ) + if args.check: + check.check_archives(args.verbosity, repository, consistency, remote_path=remote_path) + + hook.execute_hook(hooks.get('after_backup')) + except (OSError, CalledProcessError): + hook.execute_hook(hooks.get('on_error')) + raise except (ValueError, OSError, CalledProcessError) as error: print(error, file=sys.stderr) sys.exit(1) diff --git a/borgmatic/commands/hook.py b/borgmatic/commands/hook.py new file mode 100644 index 0000000..4417dea --- /dev/null +++ b/borgmatic/commands/hook.py @@ -0,0 +1,7 @@ +import subprocess + + +def execute_hook(commands): + if commands: + for cmd in commands: + subprocess.check_call(cmd, shell=True) diff --git a/borgmatic/config/generate.py b/borgmatic/config/generate.py index 1a6d1a8..2bfc3a2 100644 --- a/borgmatic/config/generate.py +++ b/borgmatic/config/generate.py @@ -24,7 +24,7 @@ def _schema_to_sample_configuration(schema, level=0): for each section based on the schema "desc" description. ''' example = schema.get('example') - if example: + if example is not None: return example config = yaml.comments.CommentedMap([ diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index d559ee3..60f6cdf 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -157,3 +157,28 @@ map: desc: Restrict the number of checked archives to the last n. Applies only to the "archives" check. example: 3 + hooks: + desc: | + Shell commands or scripts to execute before and after a backup or if an error has occurred. + IMPORTANT: All provided commands and scripts are executed with user permissions of borgmatic. + Do not forget to set secure permissions on this file as well as on any script listed (chmod 0700) to + prevent potential shell injection or privilege escalation. + map: + before_backup: + seq: + - type: scalar + desc: List of one or more shell commands or scripts to execute before creating a backup. + example: + - echo "`date` - Starting a backup job." + after_backup: + seq: + - type: scalar + desc: List of one or more shell commands or scripts to execute after creating a backup. + example: + - echo "`date` - Backup created." + on_error: + seq: + - type: scalar + desc: List of one or more shell commands or scripts to execute in case an exception has occurred. + example: + - echo "`date` - Error while creating a backup." diff --git a/borgmatic/config/validate.py b/borgmatic/config/validate.py index 3125176..ff61634 100644 --- a/borgmatic/config/validate.py +++ b/borgmatic/config/validate.py @@ -48,7 +48,7 @@ def parse_configuration(config_filename, schema_filename): # simply remove all examples before passing the schema to pykwalify. for section_name, section_schema in schema['map'].items(): for field_name, field_schema in section_schema['map'].items(): - field_schema.pop('example') + field_schema.pop('example', None) validator = pykwalify.core.Core(source_data=config, schema_data=schema) parsed_result = validator.validate(raise_exception=False) diff --git a/borgmatic/tests/unit/borg/test_hook.py b/borgmatic/tests/unit/borg/test_hook.py new file mode 100644 index 0000000..6aabc57 --- /dev/null +++ b/borgmatic/tests/unit/borg/test_hook.py @@ -0,0 +1,10 @@ +from flexmock import flexmock + +from borgmatic.commands import hook as module + + +def test_execute_hook_invokes_each_command(): + subprocess = flexmock(module.subprocess) + subprocess.should_receive('check_call').with_args(':', shell=True).once() + + module.execute_hook([':'])