Set umask used when executing hooks via "umask" option in borgmatic hooks section (#189).

This commit is contained in:
Dan Helfman 2019-06-13 17:05:26 -07:00
parent 76d79f0331
commit d6d66de251
5 changed files with 60 additions and 18 deletions

1
NEWS
View file

@ -3,6 +3,7 @@
customize the log level. See the documentation for more information: customize the log level. See the documentation for more information:
https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/ https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/
* #178: Look for .yml configuration file extension in addition to .yaml. * #178: Look for .yml configuration file extension in addition to .yaml.
* #189: Set umask used when executing hooks via "umask" option in borgmatic hooks section.
* Remove Python cache files before each Tox run. * Remove Python cache files before each Tox run.
* Add #borgmatic Freenode IRC channel to documentation. * Add #borgmatic Freenode IRC channel to documentation.
* Add Borg/borgmatic hosting providers section to documentation. * Add Borg/borgmatic hosting providers section to documentation.

View file

@ -281,7 +281,11 @@ def run_configuration(config_filename, config, args): # pragma: no cover
if args.create: if args.create:
hook.execute_hook( hook.execute_hook(
hooks.get('before_backup'), config_filename, 'pre-backup', args.dry_run hooks.get('before_backup'),
hooks.get('umask'),
config_filename,
'pre-backup',
args.dry_run,
) )
for repository_path in location['repositories']: for repository_path in location['repositories']:
@ -298,10 +302,16 @@ def run_configuration(config_filename, config, args): # pragma: no cover
if args.create: if args.create:
hook.execute_hook( hook.execute_hook(
hooks.get('after_backup'), config_filename, 'post-backup', args.dry_run hooks.get('after_backup'),
hooks.get('umask'),
config_filename,
'post-backup',
args.dry_run,
) )
except (OSError, CalledProcessError): except (OSError, CalledProcessError):
hook.execute_hook(hooks.get('on_error'), config_filename, 'on-error', args.dry_run) hook.execute_hook(
hooks.get('on_error'), hooks.get('umask'), config_filename, 'on-error', args.dry_run
)
raise raise

View file

@ -326,3 +326,7 @@ map:
desc: List of one or more shell commands or scripts to execute in case an exception has occurred. desc: List of one or more shell commands or scripts to execute in case an exception has occurred.
example: example:
- echo "Error while creating a backup." - echo "Error while creating a backup."
umask:
type: scalar
desc: Umask used when executing hooks. Defaults to the umask that borgmatic is run with.
example: 0077

View file

@ -1,4 +1,5 @@
import logging import logging
import os
from borgmatic import execute from borgmatic import execute
from borgmatic.logger import get_logger from borgmatic.logger import get_logger
@ -6,10 +7,13 @@ from borgmatic.logger import get_logger
logger = get_logger(__name__) logger = get_logger(__name__)
def execute_hook(commands, config_filename, description, dry_run): def execute_hook(commands, umask, config_filename, description, dry_run):
''' '''
Given a list of hook commands to execute, a config filename, a hook description, and whether Given a list of hook commands to execute, a umask to execute with (or None), a config filename,
this is a dry run, run the given commands. Or, don't run them if this is a dry run. a hook description, and whether this is a dry run, run the given commands. Or, don't run them
if this is a dry run.
Raise ValueError if the umask cannot be parsed.
''' '''
if not commands: if not commands:
logger.debug('{}: No commands to run for {} hook'.format(config_filename, description)) logger.debug('{}: No commands to run for {} hook'.format(config_filename, description))
@ -28,10 +32,23 @@ def execute_hook(commands, config_filename, description, dry_run):
) )
) )
for command in commands: if umask:
if not dry_run: parsed_umask = int(str(umask), 8)
execute.execute_command( logger.debug('{}: Set hook umask to {}'.format(config_filename, oct(parsed_umask)))
[command], original_umask = os.umask(parsed_umask)
output_log_level=logging.ERROR if description == 'on-error' else logging.WARNING, else:
shell=True, original_umask = None
)
try:
for command in commands:
if not dry_run:
execute.execute_command(
[command],
output_log_level=logging.ERROR
if description == 'on-error'
else logging.WARNING,
shell=True,
)
finally:
if original_umask:
os.umask(original_umask)

View file

@ -10,7 +10,7 @@ def test_execute_hook_invokes_each_command():
[':'], output_log_level=logging.WARNING, shell=True [':'], output_log_level=logging.WARNING, shell=True
).once() ).once()
module.execute_hook([':'], 'config.yaml', 'pre-backup', dry_run=False) module.execute_hook([':'], None, 'config.yaml', 'pre-backup', dry_run=False)
def test_execute_hook_with_multiple_commands_invokes_each_command(): def test_execute_hook_with_multiple_commands_invokes_each_command():
@ -21,17 +21,27 @@ def test_execute_hook_with_multiple_commands_invokes_each_command():
['true'], output_log_level=logging.WARNING, shell=True ['true'], output_log_level=logging.WARNING, shell=True
).once() ).once()
module.execute_hook([':', 'true'], 'config.yaml', 'pre-backup', dry_run=False) module.execute_hook([':', 'true'], None, 'config.yaml', 'pre-backup', dry_run=False)
def test_execute_hook_with_umask_sets_that_umask():
flexmock(module.os).should_receive('umask').with_args(0o77).and_return(0o22).once()
flexmock(module.os).should_receive('umask').with_args(0o22).once()
flexmock(module.execute).should_receive('execute_command').with_args(
[':'], output_log_level=logging.WARNING, shell=True
)
module.execute_hook([':'], 77, 'config.yaml', 'pre-backup', dry_run=False)
def test_execute_hook_with_dry_run_skips_commands(): def test_execute_hook_with_dry_run_skips_commands():
flexmock(module.execute).should_receive('execute_command').never() flexmock(module.execute).should_receive('execute_command').never()
module.execute_hook([':', 'true'], 'config.yaml', 'pre-backup', dry_run=True) module.execute_hook([':', 'true'], None, 'config.yaml', 'pre-backup', dry_run=True)
def test_execute_hook_with_empty_commands_does_not_raise(): def test_execute_hook_with_empty_commands_does_not_raise():
module.execute_hook([], 'config.yaml', 'post-backup', dry_run=False) module.execute_hook([], None, 'config.yaml', 'post-backup', dry_run=False)
def test_execute_hook_on_error_logs_as_error(): def test_execute_hook_on_error_logs_as_error():
@ -39,4 +49,4 @@ def test_execute_hook_on_error_logs_as_error():
[':'], output_log_level=logging.ERROR, shell=True [':'], output_log_level=logging.ERROR, shell=True
).once() ).once()
module.execute_hook([':'], 'config.yaml', 'on-error', dry_run=False) module.execute_hook([':'], None, 'config.yaml', 'on-error', dry_run=False)