create.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. import glob
  2. import itertools
  3. import logging
  4. import os
  5. import subprocess
  6. import tempfile
  7. from borgmatic.verbosity import VERBOSITY_SOME, VERBOSITY_LOTS
  8. logger = logging.getLogger(__name__)
  9. def initialize_environment(storage_config):
  10. passcommand = storage_config.get('encryption_passcommand')
  11. if passcommand:
  12. os.environ['BORG_PASSCOMMAND'] = passcommand
  13. passphrase = storage_config.get('encryption_passphrase')
  14. if passphrase:
  15. os.environ['BORG_PASSPHRASE'] = passphrase
  16. ssh_command = storage_config.get('ssh_command')
  17. if ssh_command:
  18. os.environ['BORG_RSH'] = ssh_command
  19. def _expand_directory(directory):
  20. '''
  21. Given a directory path, expand any tilde (representing a user's home directory) and any globs
  22. therein. Return a list of one or more resulting paths.
  23. '''
  24. expanded_directory = os.path.expanduser(directory)
  25. return glob.glob(expanded_directory) or [expanded_directory]
  26. def _expand_directories(directories):
  27. '''
  28. Given a sequence of directory paths, expand tildes and globs in each one. Return all the
  29. resulting directories as a single flattened tuple.
  30. '''
  31. if directories is None:
  32. return ()
  33. return tuple(
  34. itertools.chain.from_iterable(
  35. _expand_directory(directory)
  36. for directory in directories
  37. )
  38. )
  39. def _write_pattern_file(patterns=None):
  40. '''
  41. Given a sequence of patterns, write them to a named temporary file and return it. Return None
  42. if no patterns are provided.
  43. '''
  44. if not patterns:
  45. return None
  46. pattern_file = tempfile.NamedTemporaryFile('w')
  47. pattern_file.write('\n'.join(patterns))
  48. pattern_file.flush()
  49. return pattern_file
  50. def _make_pattern_flags(location_config, pattern_filename=None):
  51. '''
  52. Given a location config dict with a potential pattern_from option, and a filename containing any
  53. additional patterns, return the corresponding Borg flags for those files as a tuple.
  54. '''
  55. pattern_filenames = tuple(location_config.get('patterns_from') or ()) + (
  56. (pattern_filename,) if pattern_filename else ()
  57. )
  58. return tuple(
  59. itertools.chain.from_iterable(
  60. ('--patterns-from', pattern_filename)
  61. for pattern_filename in pattern_filenames
  62. )
  63. )
  64. def _make_exclude_flags(location_config, exclude_filename=None):
  65. '''
  66. Given a location config dict with various exclude options, and a filename containing any exclude
  67. patterns, return the corresponding Borg flags as a tuple.
  68. '''
  69. exclude_filenames = tuple(location_config.get('exclude_from') or ()) + (
  70. (exclude_filename,) if exclude_filename else ()
  71. )
  72. exclude_from_flags = tuple(
  73. itertools.chain.from_iterable(
  74. ('--exclude-from', exclude_filename)
  75. for exclude_filename in exclude_filenames
  76. )
  77. )
  78. caches_flag = ('--exclude-caches',) if location_config.get('exclude_caches') else ()
  79. if_present = location_config.get('exclude_if_present')
  80. if_present_flags = ('--exclude-if-present', if_present) if if_present else ()
  81. return exclude_from_flags + caches_flag + if_present_flags
  82. def create_archive(
  83. verbosity, dry_run, repository, location_config, storage_config, local_path='borg', remote_path=None,
  84. ):
  85. '''
  86. Given vebosity/dry-run flags, a local or remote repository path, a location config dict, and a
  87. storage config dict, create a Borg archive.
  88. '''
  89. sources = _expand_directories(location_config['source_directories'])
  90. pattern_file = _write_pattern_file(location_config.get('patterns'))
  91. exclude_file = _write_pattern_file(_expand_directories(location_config.get('exclude_patterns')))
  92. checkpoint_interval = storage_config.get('checkpoint_interval', None)
  93. compression = storage_config.get('compression', None)
  94. remote_rate_limit = storage_config.get('remote_rate_limit', None)
  95. umask = storage_config.get('umask', None)
  96. lock_wait = storage_config.get('lock_wait', None)
  97. files_cache = location_config.get('files_cache')
  98. default_archive_name_format = '{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}'
  99. archive_name_format = storage_config.get('archive_name_format', default_archive_name_format)
  100. full_command = (
  101. (
  102. local_path, 'create',
  103. '{repository}::{archive_name_format}'.format(
  104. repository=repository,
  105. archive_name_format=archive_name_format,
  106. ),
  107. )
  108. + sources
  109. + _make_pattern_flags(
  110. location_config,
  111. pattern_file.name if pattern_file else None,
  112. )
  113. + _make_exclude_flags(
  114. location_config,
  115. exclude_file.name if exclude_file else None,
  116. )
  117. + (('--checkpoint-interval', str(checkpoint_interval)) if checkpoint_interval else ())
  118. + (('--compression', compression) if compression else ())
  119. + (('--remote-ratelimit', str(remote_rate_limit)) if remote_rate_limit else ())
  120. + (('--one-file-system',) if location_config.get('one_file_system') else ())
  121. + (('--nobsdflags',) if location_config.get('bsd_flags') is False else ())
  122. + (('--files-cache', files_cache) if files_cache else ())
  123. + (('--remote-path', remote_path) if remote_path else ())
  124. + (('--umask', str(umask)) if umask else ())
  125. + (('--lock-wait', str(lock_wait)) if lock_wait else ())
  126. + {
  127. VERBOSITY_SOME: ('--info',) if dry_run else ('--info', '--stats'),
  128. VERBOSITY_LOTS: ('--debug', '--list', '--show-rc') if dry_run else ('--debug', '--list', '--show-rc', '--stats'),
  129. }.get(verbosity, ())
  130. + (('--dry-run',) if dry_run else ())
  131. )
  132. logger.debug(' '.join(full_command))
  133. subprocess.check_call(full_command)