test_create.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. import json
  2. import os
  3. import pytest
  4. from flexmock import flexmock
  5. import borgmatic.borg.pattern
  6. from borgmatic.actions import create as module
  7. def test_run_create_executes_and_calls_hooks_for_configured_repository():
  8. flexmock(module.logger).answer = lambda message: None
  9. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  10. flexmock(),
  11. )
  12. flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
  13. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
  14. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  15. 'call_hooks_even_if_unconfigured',
  16. ).and_return({})
  17. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  18. flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
  19. flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
  20. flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
  21. create_arguments = flexmock(
  22. repository=None,
  23. progress=flexmock(),
  24. statistics=flexmock(),
  25. json=False,
  26. comment=None,
  27. list_details=flexmock(),
  28. )
  29. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  30. list(
  31. module.run_create(
  32. config_filename='test.yaml',
  33. repository={'path': 'repo'},
  34. config={},
  35. config_paths=['/tmp/test.yaml'],
  36. local_borg_version=None,
  37. create_arguments=create_arguments,
  38. global_arguments=global_arguments,
  39. dry_run_label='',
  40. local_path=None,
  41. remote_path=None,
  42. ),
  43. )
  44. def test_run_create_with_both_list_and_json_errors():
  45. flexmock(module.logger).answer = lambda message: None
  46. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
  47. flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
  48. create_arguments = flexmock(
  49. repository=flexmock(),
  50. progress=flexmock(),
  51. statistics=flexmock(),
  52. json=True,
  53. comment=None,
  54. list_details=flexmock(),
  55. )
  56. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  57. with pytest.raises(ValueError):
  58. list(
  59. module.run_create(
  60. config_filename='test.yaml',
  61. repository={'path': 'repo'},
  62. config={'list_details': True},
  63. config_paths=['/tmp/test.yaml'],
  64. local_borg_version=None,
  65. create_arguments=create_arguments,
  66. global_arguments=global_arguments,
  67. dry_run_label='',
  68. local_path=None,
  69. remote_path=None,
  70. ),
  71. )
  72. def test_run_create_with_both_list_and_progress_errors():
  73. flexmock(module.logger).answer = lambda message: None
  74. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').never()
  75. flexmock(module.borgmatic.borg.create).should_receive('create_archive').never()
  76. create_arguments = flexmock(
  77. repository=flexmock(),
  78. progress=flexmock(),
  79. statistics=flexmock(),
  80. json=False,
  81. comment=None,
  82. list_details=flexmock(),
  83. )
  84. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  85. with pytest.raises(ValueError):
  86. list(
  87. module.run_create(
  88. config_filename='test.yaml',
  89. repository={'path': 'repo'},
  90. config={'list_details': True, 'progress': True},
  91. config_paths=['/tmp/test.yaml'],
  92. local_borg_version=None,
  93. create_arguments=create_arguments,
  94. global_arguments=global_arguments,
  95. dry_run_label='',
  96. local_path=None,
  97. remote_path=None,
  98. ),
  99. )
  100. def test_run_create_produces_json():
  101. flexmock(module.logger).answer = lambda message: None
  102. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  103. flexmock(),
  104. )
  105. flexmock(module.borgmatic.borg.create).should_receive('create_archive').once().and_return(
  106. flexmock(),
  107. )
  108. parsed_json = flexmock()
  109. flexmock(module.borgmatic.actions.json).should_receive('parse_json').and_return(parsed_json)
  110. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return({})
  111. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  112. 'call_hooks_even_if_unconfigured',
  113. ).and_return({})
  114. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  115. flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
  116. flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
  117. flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
  118. create_arguments = flexmock(
  119. repository=flexmock(),
  120. progress=flexmock(),
  121. statistics=flexmock(),
  122. json=True,
  123. comment=None,
  124. list_details=flexmock(),
  125. )
  126. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  127. assert list(
  128. module.run_create(
  129. config_filename='test.yaml',
  130. repository={'path': 'repo'},
  131. config={},
  132. config_paths=['/tmp/test.yaml'],
  133. local_borg_version=None,
  134. create_arguments=create_arguments,
  135. global_arguments=global_arguments,
  136. dry_run_label='',
  137. local_path=None,
  138. remote_path=None,
  139. ),
  140. ) == [parsed_json]
  141. def test_run_create_with_active_dumps_roundtrips_via_checkpoint_archive():
  142. mock_dump_process = flexmock()
  143. mock_dump_process.should_receive('poll').and_return(None).and_return(0)
  144. flexmock(module.logger).answer = lambda message: None
  145. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  146. flexmock(),
  147. )
  148. flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
  149. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  150. {'dump': mock_dump_process},
  151. )
  152. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  153. 'call_hooks_even_if_unconfigured',
  154. ).and_return({})
  155. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  156. flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
  157. flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
  158. flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
  159. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  160. {'id': 'id1', 'name': 'archive.checkpoint'},
  161. )
  162. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  163. flexmock(module).should_receive('rename_checkpoint_archive').with_args(
  164. repository_path='repo',
  165. global_arguments=global_arguments,
  166. config={},
  167. local_borg_version=None,
  168. local_path=None,
  169. remote_path=None,
  170. ).once()
  171. create_arguments = flexmock(
  172. repository=None,
  173. progress=flexmock(),
  174. statistics=flexmock(),
  175. json=False,
  176. comment=None,
  177. list_details=flexmock(),
  178. )
  179. list(
  180. module.run_create(
  181. config_filename='test.yaml',
  182. repository={'path': 'repo'},
  183. config={},
  184. config_paths=['/tmp/test.yaml'],
  185. local_borg_version=None,
  186. create_arguments=create_arguments,
  187. global_arguments=global_arguments,
  188. dry_run_label='',
  189. local_path=None,
  190. remote_path=None,
  191. ),
  192. )
  193. def test_run_create_with_active_dumps_json_updates_archive_info():
  194. mock_dump_process = flexmock()
  195. mock_dump_process.should_receive('poll').and_return(None).and_return(0)
  196. borg_create_result = {
  197. 'archive': {
  198. 'command_line': ['foo'],
  199. 'name': 'archive.checkpoint',
  200. 'id': 'id1',
  201. },
  202. 'cache': {},
  203. 'repository': {
  204. 'id': 'repo-id',
  205. },
  206. }
  207. expected_create_result = {
  208. 'archive': {
  209. 'command_line': ['foo'],
  210. 'name': 'archive',
  211. 'id': 'id2',
  212. },
  213. 'cache': {},
  214. 'repository': {'id': 'repo-id', 'label': ''},
  215. }
  216. flexmock(module.logger).answer = lambda message: None
  217. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  218. flexmock(),
  219. )
  220. flexmock(module.borgmatic.borg.create).should_receive('create_archive').and_return(
  221. json.dumps(borg_create_result),
  222. ).once()
  223. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').and_return(
  224. {'dump': mock_dump_process},
  225. )
  226. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  227. 'call_hooks_even_if_unconfigured',
  228. ).and_return({})
  229. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  230. flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(())
  231. flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').and_return([])
  232. flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
  233. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  234. flexmock(module).should_receive('rename_checkpoint_archive').with_args(
  235. repository_path='repo',
  236. global_arguments=global_arguments,
  237. config={},
  238. local_borg_version=None,
  239. local_path=None,
  240. remote_path=None,
  241. ).once()
  242. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  243. {'id': 'id2', 'name': 'archive'},
  244. )
  245. create_arguments = flexmock(
  246. repository=None,
  247. progress=flexmock(),
  248. statistics=flexmock(),
  249. json=True,
  250. comment=None,
  251. list_details=flexmock(),
  252. )
  253. assert list(
  254. module.run_create(
  255. config_filename='test.yaml',
  256. repository={'path': 'repo'},
  257. config={},
  258. config_paths=['/tmp/test.yaml'],
  259. local_borg_version=None,
  260. create_arguments=create_arguments,
  261. global_arguments=global_arguments,
  262. dry_run_label='',
  263. local_path=None,
  264. remote_path=None,
  265. ),
  266. ) == [expected_create_result]
  267. def mock_call_hooks(
  268. function_name, config, hook_type, config_paths, borgmatic_runtime_directory, patterns, dry_run
  269. ):
  270. '''
  271. Simulate a dump_data_sources() call that mutates the given patterns.
  272. '''
  273. mock_dump_process = flexmock()
  274. mock_dump_process.should_receive('poll').and_return(None).and_return(0)
  275. patterns[0] = borgmatic.borg.pattern.Pattern('/mutated/pattern/path')
  276. return {'dump': mock_dump_process}
  277. def mock_call_hooks_even_if_unconfigured(
  278. function_name, config, hook_type, borgmatic_runtime_directory, patterns, dry_run
  279. ):
  280. '''
  281. Assert that we're dealing with the original patterns here, not the mutated patterns.
  282. '''
  283. assert patterns[0].path == 'foo'
  284. return {}
  285. def test_run_create_with_active_dumps_removes_data_source_dumps_with_original_patterns():
  286. flexmock(module.logger).answer = lambda message: None
  287. flexmock(module.borgmatic.config.paths).should_receive('Runtime_directory').and_return(
  288. flexmock(),
  289. )
  290. flexmock(module.borgmatic.borg.create).should_receive('create_archive').once()
  291. flexmock(module.borgmatic.hooks.dispatch).should_receive('call_hooks').replace_with(
  292. mock_call_hooks
  293. )
  294. flexmock(module.borgmatic.hooks.dispatch).should_receive(
  295. 'call_hooks_even_if_unconfigured',
  296. ).replace_with(mock_call_hooks_even_if_unconfigured)
  297. flexmock(module.borgmatic.config.paths).should_receive('get_working_directory').and_return(None)
  298. flexmock(module.borgmatic.actions.pattern).should_receive('collect_patterns').and_return(
  299. (borgmatic.borg.pattern.Pattern('foo'), borgmatic.borg.pattern.Pattern('bar'))
  300. )
  301. flexmock(module.borgmatic.actions.pattern).should_receive('process_patterns').replace_with(
  302. lambda patterns, *args, **kwargs: list(patterns)
  303. )
  304. flexmock(os.path).should_receive('join').and_return('/run/borgmatic/bootstrap')
  305. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  306. {'id': 'id1', 'name': 'archive.checkpoint'},
  307. )
  308. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  309. flexmock(module).should_receive('rename_checkpoint_archive').with_args(
  310. repository_path='repo',
  311. global_arguments=global_arguments,
  312. config={},
  313. local_borg_version=None,
  314. local_path=None,
  315. remote_path=None,
  316. ).once()
  317. create_arguments = flexmock(
  318. repository=None,
  319. progress=flexmock(),
  320. statistics=flexmock(),
  321. json=False,
  322. comment=None,
  323. list_details=flexmock(),
  324. )
  325. list(
  326. module.run_create(
  327. config_filename='test.yaml',
  328. repository={'path': 'repo'},
  329. config={},
  330. config_paths=['/tmp/test.yaml'],
  331. local_borg_version=None,
  332. create_arguments=create_arguments,
  333. global_arguments=global_arguments,
  334. dry_run_label='',
  335. local_path=None,
  336. remote_path=None,
  337. ),
  338. )
  339. def test_rename_checkpoint_archive_renames_archive_using_name():
  340. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  341. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  342. {'id': 'id1', 'name': 'archive.checkpoint'},
  343. )
  344. flexmock(module.borgmatic.borg.rename).should_receive('rename_archive').with_args(
  345. repository_name='path',
  346. old_archive_name='archive.checkpoint',
  347. new_archive_name='archive',
  348. dry_run=False,
  349. config={},
  350. local_borg_version=None,
  351. local_path=None,
  352. remote_path=None,
  353. )
  354. flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(False)
  355. module.rename_checkpoint_archive(
  356. repository_path='path',
  357. global_arguments=global_arguments,
  358. config={},
  359. local_borg_version=None,
  360. local_path=None,
  361. remote_path=None,
  362. )
  363. def test_rename_checkpoint_with_feature_available_archive_renames_archive_using_id():
  364. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  365. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  366. {'id': 'id1', 'name': 'archive.checkpoint'},
  367. )
  368. flexmock(module.borgmatic.borg.rename).should_receive('rename_archive').with_args(
  369. repository_name='path',
  370. old_archive_name='id1',
  371. new_archive_name='archive',
  372. dry_run=False,
  373. config={},
  374. local_borg_version=None,
  375. local_path=None,
  376. remote_path=None,
  377. )
  378. flexmock(module.borgmatic.borg.feature).should_receive('available').and_return(True)
  379. module.rename_checkpoint_archive(
  380. repository_path='path',
  381. global_arguments=global_arguments,
  382. config={},
  383. local_borg_version=None,
  384. local_path=None,
  385. remote_path=None,
  386. )
  387. def test_rename_checkpoint_archive_checks_suffix():
  388. global_arguments = flexmock(monitoring_verbosity=1, dry_run=False)
  389. flexmock(module.borgmatic.borg.repo_list).should_receive('get_latest_archive').and_return(
  390. {'id': 'id1', 'name': 'unexpected-archive'},
  391. )
  392. with pytest.raises(
  393. ValueError,
  394. match=r'Latest archive did not have a .checkpoint suffix. Got: unexpected-archive',
  395. ):
  396. module.rename_checkpoint_archive(
  397. repository_path='path',
  398. global_arguments=global_arguments,
  399. config={},
  400. local_borg_version=None,
  401. local_path=None,
  402. remote_path=None,
  403. )