test_prune.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. import logging
  2. from collections import OrderedDict
  3. from flexmock import flexmock
  4. from borgmatic.borg import prune as module
  5. from ..test_verbosity import insert_logging_mock
  6. def insert_execute_command_mock(prune_command, output_log_level):
  7. flexmock(module.environment).should_receive('make_environment')
  8. flexmock(module).should_receive('execute_command').with_args(
  9. prune_command,
  10. output_log_level=output_log_level,
  11. borg_local_path=prune_command[0],
  12. extra_environment=None,
  13. ).once()
  14. BASE_PRUNE_FLAGS = ('--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
  15. def test_make_prune_flags_returns_flags_from_config():
  16. retention_config = OrderedDict((('keep_daily', 1), ('keep_weekly', 2), ('keep_monthly', 3)))
  17. flexmock(module.feature).should_receive('available').and_return(True)
  18. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  19. result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
  20. assert result == BASE_PRUNE_FLAGS
  21. def test_make_prune_flags_accepts_prefix_with_placeholders():
  22. retention_config = OrderedDict(
  23. (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
  24. )
  25. flexmock(module.feature).should_receive('available').and_return(True)
  26. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  27. result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
  28. expected = (
  29. '--keep-daily',
  30. '1',
  31. '--match-archives',
  32. 'sh:Documents_{hostname}-{now}*', # noqa: FS003
  33. )
  34. assert result == expected
  35. def test_make_prune_flags_with_prefix_without_borg_features_uses_glob_archives():
  36. retention_config = OrderedDict(
  37. (('keep_daily', 1), ('prefix', 'Documents_{hostname}-{now}')) # noqa: FS003
  38. )
  39. flexmock(module.feature).should_receive('available').and_return(False)
  40. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  41. result = module.make_prune_flags({}, retention_config, local_borg_version='1.2.3')
  42. expected = (
  43. '--keep-daily',
  44. '1',
  45. '--glob-archives',
  46. 'Documents_{hostname}-{now}*', # noqa: FS003
  47. )
  48. assert result == expected
  49. def test_make_prune_flags_prefers_prefix_to_archive_name_format():
  50. storage_config = {'archive_name_format': 'bar-{now}'} # noqa: FS003
  51. retention_config = OrderedDict((('keep_daily', 1), ('prefix', 'bar-')))
  52. flexmock(module.feature).should_receive('available').and_return(True)
  53. flexmock(module.flags).should_receive('make_match_archives_flags').never()
  54. result = module.make_prune_flags(storage_config, retention_config, local_borg_version='1.2.3')
  55. expected = (
  56. '--keep-daily',
  57. '1',
  58. '--match-archives',
  59. 'sh:bar-*', # noqa: FS003
  60. )
  61. assert result == expected
  62. def test_make_prune_flags_without_prefix_uses_archive_name_format_instead():
  63. storage_config = {'archive_name_format': 'bar-{now}'} # noqa: FS003
  64. retention_config = OrderedDict((('keep_daily', 1), ('prefix', None)))
  65. flexmock(module.feature).should_receive('available').and_return(True)
  66. flexmock(module.flags).should_receive('make_match_archives_flags').with_args(
  67. None, 'bar-{now}', '1.2.3' # noqa: FS003
  68. ).and_return(('--match-archives', 'sh:bar-*'))
  69. result = module.make_prune_flags(storage_config, retention_config, local_borg_version='1.2.3')
  70. expected = (
  71. '--keep-daily',
  72. '1',
  73. '--match-archives',
  74. 'sh:bar-*', # noqa: FS003
  75. )
  76. assert result == expected
  77. PRUNE_COMMAND = ('borg', 'prune', '--keep-daily', '1', '--keep-weekly', '2', '--keep-monthly', '3')
  78. def test_prune_archives_calls_borg_with_flags():
  79. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  80. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  81. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  82. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  83. insert_execute_command_mock(PRUNE_COMMAND + ('repo',), logging.INFO)
  84. prune_arguments = flexmock(stats=False, list_archives=False)
  85. module.prune_archives(
  86. dry_run=False,
  87. repository_path='repo',
  88. storage_config={},
  89. retention_config=flexmock(),
  90. local_borg_version='1.2.3',
  91. global_arguments=flexmock(log_json=False),
  92. prune_arguments=prune_arguments,
  93. )
  94. def test_prune_archives_with_log_info_calls_borg_with_info_flag():
  95. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  96. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  97. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  98. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  99. insert_execute_command_mock(PRUNE_COMMAND + ('--info', 'repo'), logging.INFO)
  100. insert_logging_mock(logging.INFO)
  101. prune_arguments = flexmock(stats=False, list_archives=False)
  102. module.prune_archives(
  103. repository_path='repo',
  104. storage_config={},
  105. dry_run=False,
  106. retention_config=flexmock(),
  107. local_borg_version='1.2.3',
  108. global_arguments=flexmock(log_json=False),
  109. prune_arguments=prune_arguments,
  110. )
  111. def test_prune_archives_with_log_debug_calls_borg_with_debug_flag():
  112. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  113. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  114. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  115. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  116. insert_execute_command_mock(PRUNE_COMMAND + ('--debug', '--show-rc', 'repo'), logging.INFO)
  117. insert_logging_mock(logging.DEBUG)
  118. prune_arguments = flexmock(stats=False, list_archives=False)
  119. module.prune_archives(
  120. repository_path='repo',
  121. storage_config={},
  122. dry_run=False,
  123. retention_config=flexmock(),
  124. local_borg_version='1.2.3',
  125. global_arguments=flexmock(log_json=False),
  126. prune_arguments=prune_arguments,
  127. )
  128. def test_prune_archives_with_dry_run_calls_borg_with_dry_run_flag():
  129. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  130. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  131. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  132. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  133. insert_execute_command_mock(PRUNE_COMMAND + ('--dry-run', 'repo'), logging.INFO)
  134. prune_arguments = flexmock(stats=False, list_archives=False)
  135. module.prune_archives(
  136. repository_path='repo',
  137. storage_config={},
  138. dry_run=True,
  139. retention_config=flexmock(),
  140. local_borg_version='1.2.3',
  141. global_arguments=flexmock(log_json=False),
  142. prune_arguments=prune_arguments,
  143. )
  144. def test_prune_archives_with_local_path_calls_borg_via_local_path():
  145. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  146. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  147. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  148. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  149. insert_execute_command_mock(('borg1',) + PRUNE_COMMAND[1:] + ('repo',), logging.INFO)
  150. prune_arguments = flexmock(stats=False, list_archives=False)
  151. module.prune_archives(
  152. dry_run=False,
  153. repository_path='repo',
  154. storage_config={},
  155. retention_config=flexmock(),
  156. local_borg_version='1.2.3',
  157. global_arguments=flexmock(log_json=False),
  158. local_path='borg1',
  159. prune_arguments=prune_arguments,
  160. )
  161. def test_prune_archives_with_remote_path_calls_borg_with_remote_path_flags():
  162. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  163. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  164. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  165. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  166. insert_execute_command_mock(PRUNE_COMMAND + ('--remote-path', 'borg1', 'repo'), logging.INFO)
  167. prune_arguments = flexmock(stats=False, list_archives=False)
  168. module.prune_archives(
  169. dry_run=False,
  170. repository_path='repo',
  171. storage_config={},
  172. retention_config=flexmock(),
  173. local_borg_version='1.2.3',
  174. global_arguments=flexmock(log_json=False),
  175. remote_path='borg1',
  176. prune_arguments=prune_arguments,
  177. )
  178. def test_prune_archives_with_stats_calls_borg_with_stats_flag_and_answer_output_log_level():
  179. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  180. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  181. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  182. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  183. insert_execute_command_mock(PRUNE_COMMAND + ('--stats', 'repo'), module.borgmatic.logger.ANSWER)
  184. prune_arguments = flexmock(stats=True, list_archives=False)
  185. module.prune_archives(
  186. dry_run=False,
  187. repository_path='repo',
  188. storage_config={},
  189. retention_config=flexmock(),
  190. local_borg_version='1.2.3',
  191. global_arguments=flexmock(log_json=False),
  192. prune_arguments=prune_arguments,
  193. )
  194. def test_prune_archives_with_files_calls_borg_with_list_flag_and_answer_output_log_level():
  195. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  196. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  197. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  198. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  199. insert_execute_command_mock(PRUNE_COMMAND + ('--list', 'repo'), module.borgmatic.logger.ANSWER)
  200. prune_arguments = flexmock(stats=False, list_archives=True)
  201. module.prune_archives(
  202. dry_run=False,
  203. repository_path='repo',
  204. storage_config={},
  205. retention_config=flexmock(),
  206. local_borg_version='1.2.3',
  207. global_arguments=flexmock(log_json=False),
  208. prune_arguments=prune_arguments,
  209. )
  210. def test_prune_archives_with_umask_calls_borg_with_umask_flags():
  211. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  212. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  213. storage_config = {'umask': '077'}
  214. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  215. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  216. insert_execute_command_mock(PRUNE_COMMAND + ('--umask', '077', 'repo'), logging.INFO)
  217. prune_arguments = flexmock(stats=False, list_archives=False)
  218. module.prune_archives(
  219. dry_run=False,
  220. repository_path='repo',
  221. storage_config=storage_config,
  222. retention_config=flexmock(),
  223. local_borg_version='1.2.3',
  224. global_arguments=flexmock(log_json=False),
  225. prune_arguments=prune_arguments,
  226. )
  227. def test_prune_archives_with_log_json_calls_borg_with_log_json_flag():
  228. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  229. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  230. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  231. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  232. insert_execute_command_mock(PRUNE_COMMAND + ('--log-json', 'repo'), logging.INFO)
  233. prune_arguments = flexmock(stats=False, list_archives=False)
  234. module.prune_archives(
  235. dry_run=False,
  236. repository_path='repo',
  237. storage_config={},
  238. retention_config=flexmock(),
  239. local_borg_version='1.2.3',
  240. global_arguments=flexmock(log_json=True),
  241. prune_arguments=prune_arguments,
  242. )
  243. def test_prune_archives_with_lock_wait_calls_borg_with_lock_wait_flags():
  244. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  245. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  246. storage_config = {'lock_wait': 5}
  247. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  248. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  249. insert_execute_command_mock(PRUNE_COMMAND + ('--lock-wait', '5', 'repo'), logging.INFO)
  250. prune_arguments = flexmock(stats=False, list_archives=False)
  251. module.prune_archives(
  252. dry_run=False,
  253. repository_path='repo',
  254. storage_config=storage_config,
  255. retention_config=flexmock(),
  256. local_borg_version='1.2.3',
  257. global_arguments=flexmock(log_json=False),
  258. prune_arguments=prune_arguments,
  259. )
  260. def test_prune_archives_with_extra_borg_options_calls_borg_with_extra_options():
  261. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  262. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  263. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  264. flexmock(module.flags).should_receive('make_repository_flags').and_return(('repo',))
  265. insert_execute_command_mock(PRUNE_COMMAND + ('--extra', '--options', 'repo'), logging.INFO)
  266. prune_arguments = flexmock(stats=False, list_archives=False)
  267. module.prune_archives(
  268. dry_run=False,
  269. repository_path='repo',
  270. storage_config={'extra_borg_options': {'prune': '--extra --options'}},
  271. retention_config=flexmock(),
  272. local_borg_version='1.2.3',
  273. global_arguments=flexmock(log_json=False),
  274. prune_arguments=prune_arguments,
  275. )
  276. def test_prune_archives_with_date_based_matching_calls_borg_with_date_based_flags():
  277. flexmock(module.borgmatic.logger).should_receive('add_custom_log_levels')
  278. flexmock(module.logging).ANSWER = module.borgmatic.logger.ANSWER
  279. flexmock(module.flags).should_receive('make_flags').and_return(())
  280. flexmock(module.flags).should_receive('make_match_archives_flags').and_return(())
  281. flexmock(module).should_receive('make_prune_flags').and_return(BASE_PRUNE_FLAGS)
  282. flexmock(module.flags).should_receive('make_flags_from_arguments').and_return(
  283. (
  284. '--newer',
  285. '1d',
  286. '--newest',
  287. '1y',
  288. '--older',
  289. '1m',
  290. '--oldest',
  291. '1w',
  292. '--match-archives',
  293. None,
  294. )
  295. )
  296. flexmock(module.flags).should_receive('make_repository_flags').and_return(('--repo', 'repo'))
  297. flexmock(module.environment).should_receive('make_environment')
  298. flexmock(module).should_receive('execute_command').with_args(
  299. (
  300. 'borg',
  301. 'prune',
  302. '--keep-daily',
  303. '1',
  304. '--keep-weekly',
  305. '2',
  306. '--keep-monthly',
  307. '3',
  308. '--newer',
  309. '1d',
  310. '--newest',
  311. '1y',
  312. '--older',
  313. '1m',
  314. '--oldest',
  315. '1w',
  316. '--match-archives',
  317. None,
  318. '--repo',
  319. 'repo',
  320. ),
  321. output_log_level=logging.INFO,
  322. borg_local_path='borg',
  323. extra_environment=None,
  324. )
  325. prune_arguments = flexmock(
  326. stats=False, list_archives=False, newer='1d', newest='1y', older='1m', oldest='1w'
  327. )
  328. module.prune_archives(
  329. dry_run=False,
  330. repository_path='repo',
  331. storage_config={},
  332. retention_config=flexmock(),
  333. local_borg_version='1.2.3',
  334. global_arguments=flexmock(log_json=False),
  335. prune_arguments=prune_arguments,
  336. )