2
0

hook.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import logging
  2. import os
  3. import requests
  4. from borgmatic import execute
  5. logger = logging.getLogger(__name__)
  6. def interpolate_context(command, context):
  7. '''
  8. Given a single hook command and a dict of context names/values, interpolate the values by
  9. "{name}" into the command and return the result.
  10. '''
  11. for name, value in context.items():
  12. command = command.replace('{%s}' % name, str(value))
  13. return command
  14. def execute_hook(commands, umask, config_filename, description, dry_run, **context):
  15. '''
  16. Given a list of hook commands to execute, a umask to execute with (or None), a config filename,
  17. a hook description, and whether this is a dry run, run the given commands. Or, don't run them
  18. if this is a dry run.
  19. The context contains optional values interpolated by name into the hook commands. Currently,
  20. this only applies to the on_error hook.
  21. Raise ValueError if the umask cannot be parsed.
  22. Raise subprocesses.CalledProcessError if an error occurs in a hook.
  23. '''
  24. if not commands:
  25. logger.debug('{}: No commands to run for {} hook'.format(config_filename, description))
  26. return
  27. dry_run_label = ' (dry run; not actually running hooks)' if dry_run else ''
  28. context['configuration_filename'] = config_filename
  29. commands = [interpolate_context(command, context) for command in commands]
  30. if len(commands) == 1:
  31. logger.info(
  32. '{}: Running command for {} hook{}'.format(config_filename, description, dry_run_label)
  33. )
  34. else:
  35. logger.info(
  36. '{}: Running {} commands for {} hook{}'.format(
  37. config_filename, len(commands), description, dry_run_label
  38. )
  39. )
  40. if umask:
  41. parsed_umask = int(str(umask), 8)
  42. logger.debug('{}: Set hook umask to {}'.format(config_filename, oct(parsed_umask)))
  43. original_umask = os.umask(parsed_umask)
  44. else:
  45. original_umask = None
  46. try:
  47. for command in commands:
  48. if not dry_run:
  49. execute.execute_command(
  50. [command],
  51. output_log_level=logging.ERROR
  52. if description == 'on-error'
  53. else logging.WARNING,
  54. shell=True,
  55. )
  56. finally:
  57. if original_umask:
  58. os.umask(original_umask)
  59. def ping_healthchecks(ping_url_or_uuid, config_filename, dry_run, append=None):
  60. '''
  61. Ping the given healthchecks.io URL or UUID, appending the append string if any. Use the given
  62. configuration filename in any log entries. If this is a dry run, then don't actually ping
  63. anything.
  64. '''
  65. if not ping_url_or_uuid:
  66. logger.debug('{}: No healthchecks hook set'.format(config_filename))
  67. return
  68. ping_url = (
  69. ping_url_or_uuid
  70. if ping_url_or_uuid.startswith('http')
  71. else 'https://hc-ping.com/{}'.format(ping_url_or_uuid)
  72. )
  73. dry_run_label = ' (dry run; not actually pinging)' if dry_run else ''
  74. if append:
  75. ping_url = '{}/{}'.format(ping_url, append)
  76. logger.info(
  77. '{}: Pinging healthchecks.io{}{}'.format(
  78. config_filename, ' ' + append if append else '', dry_run_label
  79. )
  80. )
  81. logger.debug('{}: Using healthchecks.io ping URL {}'.format(config_filename, ping_url))
  82. logging.getLogger('urllib3').setLevel(logging.ERROR)
  83. requests.get(ping_url)